Search code examples
jakarta-eetransactionsejbjava-ee-6ejb-3.1

Why does CMT commit on exit of EJB method, when transaction attribute is "Required"?


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.


Solution

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