Consider this simplified view of some code with which I'm working:
@Stateless(...)
@Remote(...)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class FirstEjbType {
@EJB(...)
private SecondEjbType secondEjb;
@EJB(...)
private ThirdEjbType thirdEjb;
public void doSomething() {
secondEjb.doSomething(); // WRITES SOMETHING TO THE DATABASE
thirdEjb.doSomething(); // CAN'T SEE THAT SOMETHING IN THE DATABASE!
}
I've set the TransactionAttribute
annotation to MANDATORY
at the class-level. I understand this to mean that all methods such as doSomething()
must be invoked within a supplied transaction. We are using container-managed transactions in this case.
The TransactionAttribute
is not used at all in SecondEjbType
or ThirdEjbType
... neither at the class nor method level. I understand this to mean that secondEjb.doSomething()
and thirdEjb.doSomething()
will both operate within the transaction supplied for firstEjb.doSomething()
.
However, I'm seriously missing out on something! As indicated by the code comments... secondEjb
writes data to a database, and thirdEjb
reads that data as part of its operation. Since all of this is running within the same transaction, I would not expect there to be any issues with isolation levels. However, for whatever reason the secondEjb
database write isn't visible to thirdEjb
.
I've turned tracing all the way up to the max, and there's apparently not an exception or error or rollback at issue... the initial write simply isn't visible to the subsequent read. I don't claim to be the world's greatest guru in transaction management... have I missed something obvious, or is my conceptual understanding basically correct and the issue may lie elsewhere?
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="pu" transaction-type="JTA">
<jta-data-source>jdbc/datasource</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="toplink.cache.shared.default" value="false"/>
</properties>
</persistence-unit>
</persistence>
First thing to check is that bean two and three use @PersistenceContext EntityManager
to get the EntityManager and not @PersistenceUnit EntityManagerFactory
followed by a createEntityManager()
call.
Second, check that the DataSource
is actually setup to participate in JTA transactions (autoCommit or related properties should be off).
Lastly, a quick and dirty way to check your propagation is to call the EntityManager.getDelegate()
method and check the resulting object is the same throughout the expected transaction scope.
Here's how things work under the covers.... The EntityManager
injected into your bean when it is created is a fake, a simple facade. When you attempt to use the EntityManager reference in a transaction, the container will actually go digging in the current transaction, find the real EntityManager that is stashed in the transaction scope and delegate your call to that EntityManager (if there is no EntityManager already in the transaction, the container will create one and add it). This real object will be the value of getDelegate()
. If the value of getDelegate()
is not the same (==) in secondEjb.doSomething()
and in thirdEjb.doSomething()
then you are not getting the expected propagation and each is talking to a different persistence context.
On a side note, beware that applying MANDATORY on the class actually only affects methods defined in that exact class, not in super classes. If you do not specify an @TransactionAttribute on a super class, those methods use the default regardless of how subclasses maybe annotated. I only mention that as it may have an impact on your understanding of your code.
I've set the TransactionAttribute annotation to MANDATORY at the class-level. I understand this to mean that all methods such as doSomething() must be invoked within a supplied transaction. We are using container-managed transactions in this case.
Using MANDATORY
at the class level means that the container should throw an exception to the caller if there is no transaction in progress when any method of FirstEjbType
is called.
Out of curiosity, who is initiating the transaction then? Is there a particular reason to not use the default REQUIRED
?
The TransactionAttribute is not used at all in SecondEjbType or ThirdEjbType... neither at the class nor method level. I understand this to mean that secondEjb.doSomething() and thirdEjb.doSomething() will both operate within the transaction supplied for firstEjb.doSomething()
This means that the default transaction attribute is used, i.e. REQUIRED
, and a REQUIRED
method is guaranteed to be executed within a transaction (the container would start one if required).
So in your case, secondEjb.doSomething()
and thirdEjb.doSomething()
should be indeed executed within the transaction of firstEjb.doSomething()
.
have I missed something obvious, or is my conceptual understanding basically correct and the issue may lie elsewhere?
The rule of thumb for (transaction-scoped) persistence context propagation is that the persistence context propagates as the JTA transaction propagates. But there are some restrictions. The JPA specification puts it like this:
5.6.3 Persistence Context Propagation
As described in section 5.1, a single persistence context may correspond to one or more JTA entity manager instances (all associated with the same entity manager factory).
The persistence context is propagated across the entity manager instances as the JTA transaction is propagated.
Propagation of persistence contexts only applies within a local environment. Persistence contexts are not propagated to remote tiers.
And Sahoo (from the GlassFish team) writes more explicitly about the propagation rules in Persistence Context propagation:
3. (rule #3) Don't expect PC to be propagated when you call a remote EJB even when the remote EJB happens to be running in the same JVM or part of the same application.
So, assuming you're using JPA everywhere, I would expect business methods called within the same transaction to inherit the same persistence context only if you aren't using Remote
interfaces (I don't know if this is a GlassFish specific interpretation of the spec but Sahoo is pretty clear about this restriction).
PS: JPA assumes a READ_COMMITTED
isolation level (so optimistic locking can work) and standard JPA does not allow custom settings of isolation levels (well, some providers do allow to change it either globally or per request but that's non portable).
PPS: I'm not convinced isolation levels are the key to your problem here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With