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.NClark, consider the following code I ran on GlassFish 3.1.1. I hope it will help in any way :-)
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class ReusingTransaction {
// It's BMT - we can't control Tx through context - must use...
@Resource
SessionContext sctx;
// ... the UserTransaction instead.
@Resource
UserTransaction utx;
// This CMT EJB will reuse BMT started transaction
@EJB
AnotherBean reuseTx;
public void testMethod() throws Exception {
// Begin Tx and check it's status - compare value with:
// http://java.sun.com/javaee/6/docs/api/constant-values.html#javax.transaction.Status.STATUS_ACTIVE
utx.begin();
System.out.println("####testMethod#### Tx status: " + utx.getStatus());
// Our BMT started a Tx - now invoke CMT and reuse this Tx
// Notice: AnotherBean has MANDATORY Tx attribute, so if no Tx would
// exist, the AnotherBean couldn't be even invoked.
reuseTx.testIt();
// Check if the CMT AnotherBean affected Tx we started.
System.out.println("####testMethod#### Tx status: " + utx.getStatus());
// Just to prevent exceptions.
utx.rollback();
}
// Implicitly CMT - must reuse the Tx
@Stateless
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public static class AnotherBean {
// It's CMT, so Tx control is made through it's context.
@Resource
SessionContext sctx;
// Can inject it, but cannot use it - will throw an Exception.
@Resource
UserTransaction utx;
public void testIt() throws Exception {
// Give a sign that rollback must be made.
sctx.setRollbackOnly();
System.out.println("####testIt#### Tx status: " + getTxStatus());
}
}
// Small hack to get the status of current thread JTA Tx
// http://java.sun.com/javaee/6/docs/api/javax/transaction/TransactionSynchronizationRegistry.html
private static int getTxStatus() throws Exception {
InitialContext ctx = new InitialContext();
TransactionSynchronizationRegistry tsr = (TransactionSynchronizationRegistry)
ctx.lookup("java:comp/TransactionSynchronizationRegistry");
return tsr.getTransactionStatus();
}
}
This EJB can be invoked i.e. from Singleton EJB with @Startup to see immediately how your AS will react.
On Glassfish 3.1.1 you'll come with the following result:
INFO: ####testMethod#### Tx status: 0
INFO: ####testIt#### Tx status: 1
INFO: ####testMethod#### Tx status: 1
Cheers!
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