Understanding EJB3 / JPA Container-Level Transactions and Isolation Levels

Consider this simplified kind of code I'm working with:

@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 set the TransactionAttribute annotation to MANDATORY at class level. I understand this means that all methods like doSomething() should be called in the incoming transaction. In this case, we use container-managed transactions.

TransactionAttribute not used at all in SecondEjbType or ThirdEjbType ... either at the class level or at the method level. I understand this means that secondEjb.doSomething() and thirdEjb.doSomething() will work in the transaction supplied for firstEjb.doSomething() .

However, I am seriously missing something! As stated in the code comments ... secondEjb writes data to the database, and thirdEjb reads this data as part of its work. Since all this works in a single transaction, I would not expect that there would be problems with isolation levels. However, for some reason, the secondEjb database secondEjb does not appear in thirdEjb .

I turned the trace to the maximum, and apparently there is no exception or error or rollback ... the original record is simply not visible for later reading. I do not pretend to be the greatest transaction management guru in the world ... have I missed something obvious or is my conceptual understanding basically correct, and the problem could be elsewhere?


UPDATE - additional information requested by johnstok below:

  • I work at GlassFish
  • I'm not sure what you mean by “non-standard flash 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>

+6
java java-ee jpa transactions
source share
5 answers

I found out a ton from all the answers here and cannot thank the people. However, I believe that my question cluttered the waters to such an extent that it is better to start with another question.

It doesn't seem like the transition from one EJB to the next and vice versa has anything to do with anything. To make things easier, I tried working with a test case that was completely isolated from one EJB. I have included this secondEjb.doSomething() method, which stores the object in the database. At the end of the method, I added em.flush() and tried to restore the object again using a JPA request.

Despite the fact that I was still in the same method in which the object was just saved , it was not visible for this subsequent request. I have done some additional research elsewhere , and it looks like this might be the usual JPA isolation mode in a transactional context. One transaction is launched, additional requests inside this transaction do not yet have visibility for uncommitted data.

If my summary of the related discussion of CodeRanch is accurate, then "yuck" on JPA! :) Anyway, I updated the code to avoid this problem.

0
source share

First of all, you need to verify that two and three bean use @PersistenceContext EntityManager to get EntityManager and not @PersistenceUnit EntityManagerFactory , followed by a call to createEntityManager() .

Second, make sure that the DataSource actually configured to participate in JTA transactions (autoCommit or related properties must be disabled).

Finally, a quick and dirty way to check your distribution is to call the EntityManager.getDelegate() method and verify that the resulting object is the same in the expected scope of the transaction.

Here's how it works under covers .... The EntityManager , introduced into your bean when it is created, is a fake, simple facade. When you try to use the EntityManager link in a transaction, the container will actually go into the current transaction, find the real EntityManager, which is enclosed in the transaction scope and delegate your call to this EntityManager (if the EntityManager is not 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() does not match (==) in secondEjb.doSomething() and thirdEjb.doSomething() , then you do not get the expected distribution, and each of them speaks with a different persistence context.

Note that applying MANDATORY to a class actually only affects the methods defined in that exact class, not superclasses. If you do not specify @TransactionAttribute in the superclass, these methods use the default value, regardless of how subclasses can be annotated. I just mention that this may affect your understanding of your code.

+15
source share

I set the TransactionAttribute annotation to MANDATORY at the class level. I understand this means that all methods like doSomething () should be called in the incoming transaction. In this case, we use container-managed transactions.

Using class-level MANDATORY means that the container must throw an exception for the caller if the transaction is not executed when the FirstEjbType method is FirstEjbType .

Out of curiosity, who initiates the transaction? Is there a special reason not to use REQUIRED by default?

TransactionAttribute is not used at all in SecondEjbType or ThirdEjbType ... either at the class level or at the method level. I understand that this means that secondEjb.doSomething () and thirdEjb.doSomething () will work in the transaction provided for firstEjb.doSomething ()

This means that the default transaction attribute is used, i.e. REQUIRED , and the REQUIRED method REQUIRED guaranteed to be executed in a transaction (the container will start alone if ).

So, in your case, secondEjb.doSomething() and thirdEjb.doSomething() should be executed in transaction firstEjb.doSomething() .

Am I missing something obvious, or is my conceptual understanding basically correct, and could the problem be elsewhere?

The rule of thumb for propagating a persistence context in a transaction is that the persistence context is propagated when a JTA transaction is propagated. But there are some limitations. The JPA specification does the following:

5.6.3 Saving Context Reproduction

As described in section 5.1, one or more persistence contexts can correspond to one or more managers of JTA object instances (all associated with the same factory object manager).

The persistence context is saved through instances of the object manager as the JTA transaction is propagated.

Persistence persistence contexts apply only in the local environment. Save contexts do not apply to remote levels.

And Sahoo (from the GlassFish team) writes in more detail about propagation rules in disseminating resilience context :

3. (rule No. 3). Do not expect the PC to spread when the remote EJB is called, even if the remote EJB is running in the same JVM or part of the same application.

So, if you use JPA everywhere, I would expect that business methods called within the same transaction will inherit the same persistence context only if you don't use Remote interfaces (I don't know if this is a specific interpretation of the GlassFish specification but Sahoo speaks quite clearly about this limitation).

PS: JPA assumes the isolation level READ_COMMITTED (so that optimization locks can work), and the standard JPA does not allow you to configure isolation levels (well, some providers allow you to change it either worldwide or by request, but not portable).

PPS: I'm not sure isolation levels are the key to your problem.

References

  • JPA 1.0 Specification
    • Section 5.6.3 “Dissemination of the sustainability context”
+12
source share

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

By default, the JPA save context is cleared until commit before executing the JPA request or manually using em.flush() . Thus, the visibility of the changes depends on the data access method used in thirdEjb - if the data is read, for example, using JDBC, the changes should not be visible without em.flush() .

+2
source share

You will need to provide additional information to answer this question.

  • Do you work in a Java EE container?
  • Have you set a custom cleaning mode?
  • Can you host the persistence.xml file?

Remember that the “persistence context” and transaction life cycle may not be the same.

Deals

In EJB 3.0, the default transaction attribute for all EJB 3.0 applications is REQUIRED . The document for the transaction type is here: http://download.oracle.com/javaee/6/api/javax/ejb/TransactionAttributeType.html

Perhaps you are using the transaction type REQUIRES_NEW , which will work in a separate transaction?

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

Flushing

With default settings, a flash is forced before the request to ensure the correct results: http://download.oracle.com/javaee/5/api/javax/persistence/FlushModeType.html

Try calling entityManager.setFlushMode(FlushModeType.AUTO); to ensure that your request failed. Enable SQL logging in your JPA provider to ensure that updates / inserts are actually sent to the database before selection.

+1
source share

All Articles