Recently, I’ve bumped into few posts on StackOverflow where people tend to compare container managed EntityManager
instances (so the one injected by the container) by invoking EntityManager#toString()
method. I’ve felt that it’s
fundamentally wrong to compare EntityManager
instances without knowing how they’re managed by the JPA provider or the
Server Application. And what if this behaviour differs between Application Server vendors?
The JPA provider provides an implementation of EntityManager
– that’s obvious. More interesting is that the
Application Server is scanning all @PersistenceContext
fields and wrapping these EntityManager
s into its own
class – a proxy – which delegates requests to the JPA provider’s EntityManager
. Therefore, if you’re comparing
results of toString()
method of such EntityManager
s, you can’t say about PersistenceContext
s equality but
rather about Server Application EntityManager equality.
Needless to say, an Application Server could use one proxy as an access point to different EntityManager
s. Without
knowing the internals, you’re not able to say how it will work and decide if your results are meaningful.
One of the ways to cope with this problem might be to unwrap the container-provided EntityManager
proxy to get to
the JPA-provided one. You can do that either using:
EntityManager#getDelegate()
which returns anObject
(this is the depreciated way) or,EntityManager#unwrap(JPAProviderSpecificClass)
which returns JPA-provider specific object (encouraged way).
In this way you’re one step closer to the victory.
On this point, it’s worth of noticing, that by the JPA specification, given Persistence Context, when used in
transactional environment, is bound to exactly one transaction and is accessible from all components which participates
in this transaction. So, maybe we could just compare if the transaction is the same and it would be enough to
confirm that the PersistenceContext
s are the same? You could check transactions for equality
using TransactionSynchronizationRegistry#getTransactionKey()
.
Both situations: unwrapping of EntityManager
and testing transactions for equality, have been presented in following code.
Moreover, this code shows one more, very important thing which might be hard to analyse. In order to invoke EJB call,
you need to use it’s business interface. If you don’t do that, you’ll end invoking a local method call which
doesn’t have any EJB nature – the container is not able to intercept such method invocation. Therefore, for
local call, the @TransactionAttribute
will have no meaning.
Take a look at the following code. I’m using EclipseLink and Glassfish 3.1.1. The client invokes method1()
.
Lets examine the exemplary results of the invocation:
INFO: [method1] Server proxy for EntityManager EM: com.sun.enterprise.container.common.impl.EntityManagerWrapper@173d62d
INFO: [method1] EclipseLink EntityManager: org.eclipse.persistence.internal.jpa.EntityManagerImpl@1f243df
INFO: [method1] Tx key: JavaEETransactionImpl: txId=45 nonXAResource=23 jtsTx=null localTxStatus=0 syncs=[com.sun.ejb.containers.ContainerSynchronization@462718, com.sun.enterprise.resource.pool.PoolManagerImpl$SynchronizationListener@1b0d75f]
INFO: [method2] Server proxy for EntityManager EM: com.sun.enterprise.container.common.impl.EntityManagerWrapper@10ba812
INFO: [method2] EclipseLink EntityManager: org.eclipse.persistence.internal.jpa.EntityManagerImpl@475614
INFO: [method2] Tx key: JavaEETransactionImpl: txId=46 nonXAResource=null jtsTx=null localTxStatus=0 syncs=[com.sun.ejb.containers.ContainerSynchronization@1644679]
INFO: [method2] Is Tx1 the same as Tx2? false
- We have 2 separate transactions (result of comparison – false).
- Application Server EntityManager wrappers – in
method1()
andmethod2()
– represents different instances. - JPA provider EntityManagers also represents different instances.
Now let’s modify the code a bit and remove or comment the @TransactionAttribute
fragment, so method2()
will have a
default Tx attribute – REQUIRED
. In this case, it will reuse the transaction of method1()
. Let’s look at the results:
INFO: [method1] Server proxy for EntityManager EM: com.sun.enterprise.container.common.impl.EntityManagerWrapper@178fd24
INFO: [method1] EclipseLink EntityManager: org.eclipse.persistence.internal.jpa.EntityManagerImpl@33517f
INFO: [method1] Tx key: JavaEETransactionImpl: txId=48 nonXAResource=95 jtsTx=null localTxStatus=0 syncs=[com.sun.ejb.containers.ContainerSynchronization@12b86b2, com.sun.enterprise.resource.pool.PoolManagerImpl$SynchronizationListener@985158]
INFO: [method2] Server proxy for EntityManager EM: com.sun.enterprise.container.common.impl.EntityManagerWrapper@469bc
INFO: [method2] EclipseLink EntityManager: org.eclipse.persistence.internal.jpa.EntityManagerImpl@33517f
INFO: [method2] Tx key: JavaEETransactionImpl: txId=48 nonXAResource=95 jtsTx=null localTxStatus=0 syncs=[com.sun.ejb.containers.ContainerSynchronization@12b86b2, com.sun.enterprise.resource.pool.PoolManagerImpl$SynchronizationListener@985158]
INFO: [method2] Is Tx1 the same as Tx2? true
- We have 1 transaction (result of comparison – true).
- Application Server EntityManager wrappers represents different instances.
- Both JPA provider EntityManagers represents the same instance.
So, as you can see – only after unwrapping the container-provided EntityManager
we were able to see that we, in
fact, use the same EntityManager.
At the end, let’s see get back to the original code but change the invocation of method2()
from method1()
to be a
local call, so we’ll be using myMethod2(txKey)
instead of ctx.getBusinessObject(MyEJB.class).myMethod2(txKey)
.
Exemplary results might be as follows:
INFO: [method1] Server proxy for EntityManager EM: com.sun.enterprise.container.common.impl.EntityManagerWrapper@110d926
INFO: [method1] EclipseLink EntityManager: org.eclipse.persistence.internal.jpa.EntityManagerImpl@12062da
INFO: [method1] Tx key: JavaEETransactionImpl: txId=50 nonXAResource=95 jtsTx=null localTxStatus=0 syncs=[com.sun.ejb.containers.ContainerSynchronization@139defe, com.sun.enterprise.resource.pool.PoolManagerImpl$SynchronizationListener@5f299d]
INFO: [method2] Server proxy for EntityManager EM: com.sun.enterprise.container.common.impl.EntityManagerWrapper@110d926
INFO: [method2] EclipseLink EntityManager: org.eclipse.persistence.internal.jpa.EntityManagerImpl@12062da
INFO: [method2] Tx key: JavaEETransactionImpl: txId=50 nonXAResource=95 jtsTx=null localTxStatus=0 syncs=[com.sun.ejb.containers.ContainerSynchronization@139defe, com.sun.enterprise.resource.pool.PoolManagerImpl$SynchronizationListener@5f299d]
INFO: [method2] Is Tx1 the same as Tx2? true
- We have 1 transaction (result of comparison – true).
- Both Application Server EntityManager wrappers represents the same instance.
- Both JPA provider EntityManagers represents the same instance.
I hope this sum up what are possible situations you might bump into when using transactional, container-managed EntityManager
.
The code snippet which I presented here can be found on my Gist.
References: