I'm trying to inject an EntityManager
into a jersey resource and into dependent repository services. The idea is to start and commit a transaction at the boundaries of the REST request, so the same entity manager must be used. To achieve this, I've created a custom HK2 scope @TransactionScope
, using an operation.
The application is running as Java SE application, the jersey resources being only one part of it. Other services run in the background, triggered through schedulers or JMS, and they also use the same scope. Java 11, current versions of jersey, HK2, Eclipselink, etc.
The scope is defined @Proxiable
:
@Scope
@Proxiable(proxyForSameScope = false)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TransactionScope { }
The creation of the EntityManager
is registered in the root servicelocator using the scope:
bindFactory(EMFactory.class)
.to(EntityManager.class)
.in(TransactionScope.class);
Repository services that use the EntityManager
are @Singleton
. These provide the CRUD operations around database entities. Thanks to the proxied injection, they get the same entity manager while in the same scope.
The idea is:
- At the transaction boundary, start the HK2 operation. For a jersey REST request, this is implemented through a
ContainerRequestFilter
; for a background service, it is the scheduler that runs a job, or the message listener that receives a JMS message. - The jersey resource/scheduled job/message processor begins the database transaction
entityManager.getTransaction().begin()
, then executes is specific logic. - Repository services are injected and provide CRUD operations for the database entities.
- The transaction is committed.
- The HK2 operation is closed.
All of this works fine when injecting the entity manager into the background services. But as soon as I attempt to inject it into a jersey resource, an IncompatibleClassChangeError
is thrown.
ERROR c.s.s.e.RuntimeExceptionMapper - Caught A MultiException has 2 exceptions. They are:
1. java.lang.IncompatibleClassChangeError: class javax.persistence.EntityManager_$$_jvsted_0 has interface javax.persistence.EntityManager as super class
2. java.lang.IllegalArgumentException: While attempting to create a Proxy for javax.persistence.EntityManager in scope com.skalio.rsem.TransactionScope an error occured while creating the proxy
at org.jvnet.hk2.internal.ProxyUtilities.generateProxy(ProxyUtilities.java:211)
Researching this error, it is often recommended to check for duplicate and conflicting libraries on the classpath. I believe this is not the case here (at least I can't find anything).
I cannot use @RequestScoped
, as it is only available to the jersey resources, not the background services.
Any suggestions what I can do?
Update
I've noticed that the following works:
@TransactionScope
is marked@Unproxiable
- Injection of the
EntityManager
occurs via ajavax.inject.Provider
This is possible way forward for me, even though I find it not as clean. A developer could forget that Provider-injection must be used.
Update regarding dependencies
I've looked more into the dependencies. Slight surprise for me: EclipseLink does not use javax.persistence:javax.persistence-api
but instead org.eclipse.persistence:jakarta.persistence
. Regardless, that is still the only place on the classpath containing javax.persistence
packages.
[INFO] +- org.eclipse.persistence:eclipselink:jar:2.7.7:compile
[INFO] | +- org.eclipse.persistence:jakarta.persistence:jar:2.2.3:compile
[INFO] | \- org.eclipse.persistence:commonj.sdo:jar:2.1.1:compile
[INFO] +- org.eclipse.persistence:org.eclipse.persistence.jpa.modelgen.processor:jar:2.7.7:provided
[INFO] | +- org.eclipse.persistence:org.eclipse.persistence.core:jar:2.7.7:provided
[INFO] | | \- org.eclipse.persistence:org.eclipse.persistence.asm:jar:2.7.7:provided
[INFO] | \- org.eclipse.persistence:org.eclipse.persistence.jpa:jar:2.7.7:provided
[INFO] | +- org.eclipse.persistence:org.eclipse.persistence.antlr:jar:2.7.7:provided
[INFO] | \- org.eclipse.persistence:org.eclipse.persistence.jpa.jpql:jar:2.7.7:provided
Update regarding service locators
Since only part of the code lives in jersey and the rest in the "background", I register as much as possible in the "root" ServiceLocator
. To give jersey access, I bridge the locator from jersey into the root locator.
// configures the jersey container
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig(ServiceLocator parentLocator) {
// These services need to be registered into jersey's locator
register(new JerseyContainerBinder());
// bridge services into jersey
register(new ServiceLocatorBridge(parentLocator));
}
}
The JerseyContainerBinder
is currently empty, I can use it to extend or override root-locator-bindings. The ServiceLocatorBridge
fetches jersey's locator and connect it: ExtrasUtilities.bridgeServiceLocator(jerseyLocator, sourceLocator);
So this is where there is a difference between injecting something in the background compared to in a jersey resource: it has to go through the bridge first. Not sure if that matters. Ah, if the jersey folks would only allow reusing an existing locator...