9

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.

Tiny
  • 27,221
  • 105
  • 339
  • 599
nclark
  • 1,022
  • 1
  • 11
  • 16
  • "*If there's one already there, it will politely leave it uncommitted.*" Are you sure about your perception here? From your accepted answer, "*The `UserTransaction` I started has remained open, but CMT created a new transaction on entry to the EJB method, despite the "Required" attribute.*" This is an attempt to create another **nested transaction** and should instead cause an exception like -- `javax.transaction.NotSupportedException: Nested transaction not supported.`, since nested transactions are not supported in Java EE. I got misled completely about what I already knew about CMT and BMT. – Tiny Aug 29 '15 at 05:59
  • Can you remember the test scenario used fours ago? – Tiny Aug 29 '15 at 06:00
  • @Tiny - I remember validating in multiple ways both that my UserTransaction remained open and that the CMT transaction was committed on exit from the container-managed EJB. This understanding allowed me to accommodate the situation successfully, if not gracefully. I never found anything to indicate that WL had been proven to behave otherwise (note that the tests cited by DavidBlevens used MANDATORY according to him, not REQUIRED as was my case). This was 3-4 contracts ago, so I can't give you any more than that, sorry. – nclark Aug 30 '15 at 15:39
  • I apologize for the misspelling of @DavidBlevins. Apparently I can no longer fix it because 5 minutes have gone by. – nclark Aug 30 '15 at 15:48

4 Answers4

22

REQUIRED can be evil

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.

MANDATORY and UserTransaction are soul mates

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.

Considerations

  • A Servlet or a BMT EJB can use a UserTransaction.
  • CMT beans cannot use a UserTransaction, but they can participate in a transaction started by a UserTransaction (as noted above, a JTA transaction is a JTA transaction).
  • BMT beans cannot participate in an existing transaction. If you call a BMT bean while a transaction is already in progress, the transaction will be suspended by the container prior to invoking the BMT bean and resumed after the method completes.
  • @Resource UserTransaction will get you the user transaction via injection
  • java: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)
  • If you get sick of calling userTransaction.begin() and userTransaction.commit() and doing the related exception handling, consider @TransactionAttribute(REQUIRES_NEW). Your transaction boundaries will still be documented and explicit.
  • RuntimeExceptions 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.

Losing your Transaction context

A couple things can cause your in-progress transaction to stop, suspend or otherwise not propagate to the called bean.

  • BMT beans stop transaction propagation. If a transaction in progress calls a BMT bean, that transaction will be suspended before the BMT bean is called and will be resumed after the bean returns. BMT beans can be the origin of transactions but cannot participate in existing transactions. If propagation is mysteriously failing, make sure there are not unintentional calls to BMT beans in mid-transaction.
  • Do not use anything other than a container-supplied UserTransaction or container supplied methods like SessionContext.setRollbackOnly to manage transactions. Use of a "resource-manager specific transaction demarcation API" such as JPA EntityTransaction or java.sql.Connection.commit() will circumvent transaction management.
  • Do not start your own threads. Transaction propagation happens on a per-thread basis. There are no standard APIs in Java EE that support a transaction spanning multiple threads. If you leave the thread either from starting your own thread or using @Asynchronous, you will leave your existing transaction behind.
Community
  • 1
  • 1
David Blevins
  • 19,178
  • 3
  • 53
  • 68
  • "CMT beans cannot use a UserTransaction, but they can participate in a transaction started by a UserTransaction (as noted above, a JTA transaction is a JTA transaction)." Common sense tells me this should be true, but it's not what I'm observing under WL 10.3, and I haven't found any clear statement about it in the CMT specs, other than the rather vague warning against using the UserTransaction API with CMT. – nclark Aug 29 '11 at 14:47
  • Nice post BTW. I agree with your point about explicit transactional boundaries. The biggest problem I'm having with this legacy app is that the back-end logic is overly complex, and transaction boundaries were assigned by blindly sticking "Required" on every EJB in the system. The result is dozens of transactions per user request! – nclark Aug 29 '11 at 14:52
  • 1
    The spec is very clear on it in EJB 3.1 section 13.6.2.2. When it says "transaction context" it means it emphatically and without exception. There are tests in the Java EE TCK that specifically go from UserTransaction to a CMT bean with MANDATORY. These particular tests are very old and WLS 10.3 will have passed them. Make sure you are not digging into hibernate specific APIs that affect transactions. The spec explicitly states apps should not use the "resource-manager specific transaction demarcation API". – David Blevins Aug 29 '11 at 20:29
  • I don't think REQUIRED is that evil. It's correct that we can't make sure REQUIRED will start a new transaction or not, because REQUIRED leaves this decision to the caller. If you call REQUIRED without transaction, it will start a new one, otherwise, it will uses the existed one. This is not that bad IMHO, just use it in the right situation, as other TransactionAttributes. – Nier Apr 14 '16 at 03:26
  • @DavidBleins Great post, not sure how I missed so far, upvoted right away, will test out ASAP what MANDATORY has to offer in various places I think it may help. I have one question though, regarding the last bullet point in your answer, "Do not start your own threads". Well, I am considering starting my own threads in one of the integrations my company builds for a proprietary enterprise app. And the reason is exactly as you stated, to run on a separate transaction. Why can't I use the REQUIRES_NEW you might ask, I am using it, though I have to integrate my plugin using the event listener .. – alegria Mar 21 '21 at 14:09
  • @DavidBleins ... mechanism of this enterprise app, and it seems it is called in the same transaction where it was fired in the session bean inside the app. Problem is, I am trying to change the content of the row of the very table this transaction holds. Consequently I get exceptions due to either a snapshot isolation violation or some mysterious reason I have yet to discover. What I have in mind as a solution, is to fire a separate thread, let it wait a little (so the existing transaction commits) then do my thing on the table on a fresh transaction. I would appreciate your input on this – alegria Mar 21 '21 at 14:16
  • @DavidBlevins _fix typo in mentions above_ – alegria Mar 22 '21 at 06:28
2

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!

Community
  • 1
  • 1
Piotr Nowicki
  • 17,914
  • 8
  • 63
  • 82
1

Closer inspection reveals a different answer than suggested above. What I'm actually seeing is that the UserTransaction I started has remained open, but CMT created a new transaction on entry to the EJB method, despite the "Required" attribute.

I believe this is happening because I broke the rules. :) You aren't supposed to access the UserTransaction API when using CMT. CMT happily ignored my UserTransaction and started its own, having taken its place as the arbiter of all transaction boundaries. Since it started the transaction, it committed it as well, and of course left my UserTransaction untouched.

Seems brittle and silly to me, perhaps a naive opinion, but appears consistent with the "rules" as I read them. I don't know why CMT chooses not to play nice with UserTransactions started at a higher level. Perhaps to force developers to "do the right J2EE thing" and create another session bean layer to handle the broader transaction context. This would work because CMT would be managing the outer transaction and therefore would be fine with enlisting any inner transactions, and I believe in such a case, the "umbrella" transaction would not be committed by the inner EJBs; CMT would wait until the outer transaction completes, then commit the whole thing. It would have to, actually.

I'm not in the mood to create more session EJBs in this already EJB-bloated app, but it may be the only solution short of ripping CMT out in a whole bunch of places I'd rather not touch.

nclark
  • 1,022
  • 1
  • 11
  • 16
  • No I'm confused :). I can be wrong but in my opinion if you have @Required attribute on the bean and you call it from client which uses UserTransaction then the transaction ctx gets propagated but once the method returns the container is in charge and commits the transaction because its a CMT bean. Again, using CMT container commits the tranasction as I have written so there is nothing unusual in it. – Kris Aug 26 '11 at 17:04
  • nclark, I'm a bit puzzled by your note. Perhaps if you post some sample code we could help decipher where you might be breaking the rules. It may also help to know what container you're running on. Typically speaking, if you use a UserTransaction in the wrong spot the container will spew exceptions for you. – John Ament Aug 28 '11 at 02:06
  • @John - I think we may need a team of lawyers to interpret the language used in the spec and elsewhere, but I'm referencing the [Sun J2EE tutorials](http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/Transaction3.html): _You should not invoke any method that might interfere with the transaction boundaries set by the container..._ – nclark Aug 29 '11 at 14:24
  • @Kris - I accepted your interpretation of this at first, but again re-reading the J2EE tutorials verbiage you quote below, it may appear to promise a commit on every EJB exit, but I think the word "typically" is really meant to extend to committing as well as beginning the transaction. If you think about it, if the commit always happened, you could never enlist multiple EJBs in an umbrella transaction, because the first one would always commit when done. – nclark Aug 29 '11 at 15:00
0

That's how CMT managed transactions work. The container automatically commits the transaction when the business method returns. If you dont' wont this behaviour use BMT instead of CMT.

Kris
  • 5,714
  • 2
  • 27
  • 47
  • Thanks for the response Kris. But this seems like an utterly un-useful way for CMT to behave when either "Required" or "Supported" is specified, when there's already a transaction in progress. I can't find any explanation of CMT transactions that confirms this. Would you have a link, by any chance? – nclark Aug 26 '11 at 13:29
  • 1
    "Typically, the container begins a transaction immediately before an enterprise bean method starts. It commits the transaction just before the method exits. Each method can be associated with a single transaction. Nested or multiple transactions are not allowed within a method. " - copied from here: http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/Transaction3.html – Kris Aug 26 '11 at 13:33
  • Yes, I was just re-reading that exact paragraph. (Sorry, can't upvote.) I must admit, it doesn't call out any difference depending on the transaction attribute. To me this is a glaring omission and renders 50% of the transaction scenarios I can think of quasi-unimplementable. Maybe I just don't "get it" :). – nclark Aug 26 '11 at 13:47
  • I think I'm going to try to migrate this legacy app I'm struggling with away from CMT. To me, the EJB method is typically too granular a transaction scope anyhow, and also messes with Hibernate lazy loading in the app layer, since it typically falls outside the transaction. I've seen suggestions that one start and end the transaction in a servlet filter, which is nice for happy-path "commit" scenarios, but I'd rather address "rollback" scenarios in the control layer where error messaging is most cleanly and flexibly handled. Comments on this strategy, anyone? – nclark Aug 26 '11 at 13:47
  • I now believe my original expectation was correct, and that the answer Kris has given is not, though his response was based on a picture I drew that was both partial and incorrect. See below for my conclusion. – nclark Aug 26 '11 at 16:09