Understanding EJB3/JPA container-level transactions and isolation level
Asked Answered
J

5

6

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?


UPDATE - Additional information requested by johnstok below:

  • I am running in GlassFish
  • I'm not sure what you mean by "non-standard flush mode", so I assume the answer is no.
  • My persistence.xml file looks like this:

<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>

Jade answered 9/11, 2010 at 17:54 Comment(2)
Ask yourself - "When does the commit occur?" Oh, and in conjunction with that "What is my transaction isolation level?"Philine
I added details on flush modes below. What annotations are on the 2nd & 3rd EJBS? Can you try REQUIRED rather than MANDATORY?Bottoms
J
0

I've learned a ton from all the answers here, and can't thank people enough. However, I believe that my question has muddied the waters to a point where it might be better to start over with a different question.

It doesn't appear that jumping from one EJB to the next and back has anything to do with anything. To simplify matters, I tried working with a test case completely isolated to one EJB. I went into that secondEjb.doSomething() method, which persists an entity to the database. At the end of the method, I added an em.flush(), and tried retrieving the entity again with a JPA query.

Even though I was still in the exact same method where the entity had just been persisted, it was not visible to that subsequent query. I've done some additional research elsewhere, and it looks like this may simply be the normal mode of isolation for JPA in a transactional context. One a transaction is started, additional queries within that transaction don't yet have visibility to uncommitted data.

If my summary of the linked CodeRanch discussion is accurate, then "yuck" on JPA! :) Either way, I've refactored the code to avoid this issue altogether.

Jade answered 10/11, 2010 at 21:40 Comment(7)
I don't believe your summary of the coderanch thread is accurate. Uncommitted data are of course "visible" from subsequent queries within a transaction. Everything would get wrong if they weren't, nothing could work.Inpour
I agree... but nevertheless, I have uncommitted data not visible to subsequent queries within the same method. To be fair though, the "write" operation in that method invokes a helper class which in turn invokes a DAO layer... so who knows what subtle detail may be tripping me up (the method's transaction should propagate downward to that helper class and DAO, since there are no Remote interfaces involved). Short of posting a tarball of my entire codebase, this may just be a question beyond the scope appropriate for StackOverflow.Jade
Is this DAO using an EntityManager?? If yes, how is this EntityManager obtained (is it passed through from the EJB where it is injected to the DAO?)? If not, how is the database connection obtained? from the JTA datasource used by JPA? Too much details are missing IMO. But I still think your summary of the coderanch thread is inaccurate.Inpour
The DAO layer is using an EntityManager, which is obtained through EJB dependency injection.Jade
Hmm... do you believe that CodeRanch thread is inaccurate, or that my summary of that thread is inaccurate. If the latter, what do you read that thread as actually saying?Jade
This part (...) and it looks like this may simply be the normal mode of isolation for JPA in a transactional context. One a transaction is started, additional queries within that transaction don't yet have visibility to uncommitted data. can't be true, at least not as a general statement. See section 3.6.2 Queries and FlushMode of the spec. Regarding the thread on coderanch, I don't think there is anything to conclude from it.Inpour
Just to confirm what Pascal said: data committed in a transaction is visible in that transaction (I have clearly tested it). Besides, there could simply be a bug in your application server. Also, if you use any Caching (on JPA level), I would try to disable it.Gamogenesis
T
15

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.

Trompe answered 10/11, 2010 at 17:28 Comment(2)
+1 I always enjoy reading details about the behavior under the hood. Thanks.Inpour
great points, David. I sure have learnt something from this post.Lewls
I
13

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.

References

  • JPA 1.0 Specification
    • Section 5.6.3 "Persistence Context Propagation"
Inpour answered 10/11, 2010 at 2:1 Comment(3)
+1 Excellent point on the Remote interface. Remote interface calls are not guaranteed to happen in the same thread, if not that breaks propagation right there. Second, any arguments passed in or returned will always be serialized and that will cause JPA entities to detach if they are passed through remote interfaces.Trompe
@David Blevins: I don't THINK this has any bearing on me, as I'm using Local interfaces only for SecondEjbType and ThirdEjbType. However, I am using a Remote interface for the FirstEjbType entry point. You asked why it uses the TransactionAttributeType.MANDATORY annotation... and it's because this entry point is intended to be called only by remote EJB clients that are managing their own transaction (I wasn't the original author of all this). Either way, thanks for the info... I had no idea about this distinction between Local and Remote.Jade
It's the most helpful answer for me. Pascal, you are a Golden Man! I injected EJBs locally using remote interfaces and could not understand the cause.Gyre
F
2

Since is mentioned in the tags, I guess that persistence context is not flushed before calling thirdEjb methods, so changes are not written to the database.

By default, JPA persistence context is flushed before commit, before execution of a JPA query or manually with em.flush(). So, visibility of changes depends on the data access method used in thirdEjb - if data are read, for example, using JDBC, changes shouldn't be visible without em.flush().

Faustina answered 9/11, 2010 at 18:39 Comment(1)
Excellent point! However, we are in fact calling em.flush() after the database write in the first method.Jade
B
1

You'll need to provide more information to get this question answered.

  • Are you running in a Java EE container?
  • Have you set a non-standard flush mode?
  • Can you post your persistence.xml file?

Be aware that 'persistence context' and transaction lifecycle may not be the same.

Transactions

Per EJB 3.0 the default transaction attribute for all EJB 3.0 applications is REQUIRED. The doc's for transaction type are here: http://download.oracle.com/javaee/6/api/javax/ejb/TransactionAttributeType.html

Are you perhaps using the REQUIRES_NEW transaction type which will operate in a separate transaction?

In particular, try using a local rather than remote interface for the 2nd and 3rd EJBs.

Flushing

With default settings a flush is forced before a query (if needed) to ensure results are correct: http://download.oracle.com/javaee/5/api/javax/persistence/FlushModeType.html

Try calling entityManager.setFlushMode(FlushModeType.AUTO); to make sure a flush happens before your query. Enable SQL logging in your JPA provider to be sure that the updates / inserts are really being sent to the DB prior to the select.

Bottoms answered 9/11, 2010 at 19:3 Comment(1)
I updated the question above to reflect the three bullet-points you asked about. We are not using REQUIRES_NEW anywhere.Jade
J
0

I've learned a ton from all the answers here, and can't thank people enough. However, I believe that my question has muddied the waters to a point where it might be better to start over with a different question.

It doesn't appear that jumping from one EJB to the next and back has anything to do with anything. To simplify matters, I tried working with a test case completely isolated to one EJB. I went into that secondEjb.doSomething() method, which persists an entity to the database. At the end of the method, I added an em.flush(), and tried retrieving the entity again with a JPA query.

Even though I was still in the exact same method where the entity had just been persisted, it was not visible to that subsequent query. I've done some additional research elsewhere, and it looks like this may simply be the normal mode of isolation for JPA in a transactional context. One a transaction is started, additional queries within that transaction don't yet have visibility to uncommitted data.

If my summary of the linked CodeRanch discussion is accurate, then "yuck" on JPA! :) Either way, I've refactored the code to avoid this issue altogether.

Jade answered 10/11, 2010 at 21:40 Comment(7)
I don't believe your summary of the coderanch thread is accurate. Uncommitted data are of course "visible" from subsequent queries within a transaction. Everything would get wrong if they weren't, nothing could work.Inpour
I agree... but nevertheless, I have uncommitted data not visible to subsequent queries within the same method. To be fair though, the "write" operation in that method invokes a helper class which in turn invokes a DAO layer... so who knows what subtle detail may be tripping me up (the method's transaction should propagate downward to that helper class and DAO, since there are no Remote interfaces involved). Short of posting a tarball of my entire codebase, this may just be a question beyond the scope appropriate for StackOverflow.Jade
Is this DAO using an EntityManager?? If yes, how is this EntityManager obtained (is it passed through from the EJB where it is injected to the DAO?)? If not, how is the database connection obtained? from the JTA datasource used by JPA? Too much details are missing IMO. But I still think your summary of the coderanch thread is inaccurate.Inpour
The DAO layer is using an EntityManager, which is obtained through EJB dependency injection.Jade
Hmm... do you believe that CodeRanch thread is inaccurate, or that my summary of that thread is inaccurate. If the latter, what do you read that thread as actually saying?Jade
This part (...) and it looks like this may simply be the normal mode of isolation for JPA in a transactional context. One a transaction is started, additional queries within that transaction don't yet have visibility to uncommitted data. can't be true, at least not as a general statement. See section 3.6.2 Queries and FlushMode of the spec. Regarding the thread on coderanch, I don't think there is anything to conclude from it.Inpour
Just to confirm what Pascal said: data committed in a transaction is visible in that transaction (I have clearly tested it). Besides, there could simply be a bug in your application server. Also, if you use any Caching (on JPA level), I would try to disable it.Gamogenesis

© 2022 - 2024 — McMap. All rights reserved.