I am consistently finding that my already-existing transaction is getting committed inside any method of an EJB marked @ejb.transaction
type="Required"
. Can this be correct?
My expectation is, an EJB "requiring" a transaction means: if there's one already there, it will politely leave it uncommitted when done so that whoever invoked begin() can continue to use it for further operations before invoking commit()
or rollback()
. [Of course, if there was no transaction in the first place, then the EJB method would invoke both begin()
and commit()
/rollback()
.]
Is my expectation wrong, or should I be looking for a configuration bug?
It might be relevant to add that I'm using Hibernate 3 inside the EJB. I'm obtaining a UserTransaction before calling the EJB method. The EJB generated wrapper invokes ServerTransaction.commit()
on exit, which Hibernate hooks into and uses the opportunity to close its Session. The error I'm getting is a Hibernate lazy loading exception, because the session is closed when I try to access getters on a Hibernate-persisted object. So technically, I'm not 100% sure whether the ServerTransaction.commit()
I observed necessarily committed the UserTransaction
I started (maybe ServerTransaction.commit()
doesn't always actually follow through with a "real" commit?), but if it did not -- then on what basis is Hibernate closing the Session?
Update: I believe my assumptions above were correct, but my observations were a bit off. See below for my self-supplied Answer.
I personally do not like the REQUIRED transaction attribute and would strongly discourage its use.
Lazily creating transactions (which is what REQUIRED does) results in not really knowing when and where a transaction was actually started and when it will commit. That's not a good thing. People should explicitly design transactional boundaries.
Your desire to use a UserTransaction
is very good and does work with CMT -- there is no difference between a JTA transaction started via UserTransaction supplied by the container or a JTA transaction started for you by the container.
The approach I would recommend is to switch all REQUIRED usage to MANDATORY. With MANDATORY the container will not start transactions for you. Instead it will protect your bean by ensuring it cannot be called unless a transaction is in progress. This is an amazing and underutilized feature. MANDATORY is the best friend of anyone who wants to create a truly deterministic transactional app and to enforce it. With this setup you might fall in love with CMT.
In this scenario you start the transactions with UserTransactions
and then the container is like your big bodyguard kicking people to the curb unless they've appropriately started a transaction before attempting to invoke your code.
@Resource UserTransaction
will get you the user transaction via injectionjava:comp/UserTransaction
will get you the user transaction via lookup@TransactionAttribute(MANDATORY)
used at the class level will affect the methods of that exact class (i.e. the fooClass.getDecaredMethods()
methods). Methods of super classes and subclasses will default to @TransactionAttribute(REQUIRED)
unless those classes are also explicitly annotated @TransactionAttribute(MANDATORY)
userTransaction.begin()
and userTransaction.commit()
and doing the related exception handling, consider @TransactionAttribute(REQUIRES_NEW)
. Your transaction boundaries will still be documented and explicit.RuntimeException
s thrown from a method of a CMT bean will cause the transaction to be marked for rollback, even if you catch and handle the exception in the calling code. Use @ApplicationException
to disable this on a case by case basis for custom runtime exception classes.A couple things can cause your in-progress transaction to stop, suspend or otherwise not propagate to the called bean.
EntityTransaction
or java.sql.Connection.commit()
will circumvent transaction management.