Container Managed Transaction – Mind the interface

We‘ve been using EJBs for a long time now and, thanks to Java EE 5, it‘s today even easier to do so. What happens when you are comfortable with something ? You forget the basics. And the basics are, J2EE 1.4 or Java EE 5, an EJB runs inside a container. This container can do a lot for you, transactions for example, but you have to make sure you use the container. Let give me an example of a common mistake.

Imagine that you have two Stateless EJBs, each one persisting an object. To make it simple I‘ll call them ABean (persists a A object) and BBean (persists a B object). Here is how it works :

1 – The ABean.createsA() method starts a transaction and calls the BBean.createsB() method (1)
2 – The BBean.createsB() method persists a B object and returns it (2)
3 – The ABean persists a A object using B, commit all changes and end the transaction (3)

Everything should be smooth, your data is committed, and you‘ll get an A and a B in your database.

What happens if the BBean.createsB() method uses TransactionAttributeType.NEVER ? ABean starts a transactions (_TransactionAttributeType.REQUIRED_ is the default), calls BBean but BBean refuses to be part of the transaction and throws an Exception (_RemoteException: EJB cannot be invoked in global transaction_). The transaction is rolled back, no A or B objects are persisted in your database, great, CMT works fine, just the way we expect it to work.


@Stateless
public class ABean implements ARemote, ALocal {

    @PersistenceContext private EntityManager em;
    @EJB private BLocal bBean;

    public void createsA() {
        // Calls the BBean
        B b = bBean.createsB(); (1)
        A a = new A(b);
        em.persist(a); (3)
    }
}

@Stateless
public class BBean implements BLocal {

    @PersistenceContext private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.NEVER)
    public B createsB() {
        B b = new B();
        em.persist(b);  (2)
        return b;
    }
}

Now, forget about the BBean and move the createsB() method into the ABean (4). The createsA() just makes a local call to createsB() (5) which still uses TransactionAttributeType.NEVER What do you think will happen ? Nothing, A and B will be persisted without any exception. Why ? Because the container wasn’t involved in this call, so the TransactionAttributeType.NEVER wasn‘t worrying him.

@Stateless
public class ABean implements ARemote, ALocal {

    @PersistenceContext private EntityManager em;

    public void createsA() {
        // local call
        B b = createsB(); (5)
        A a = new A(b);
        em.persist(a);
    }

    @TransactionAttribute(TransactionAttributeType.NEVER)
    public B createsB() {  (4)
        B b = new B();
        em.persist(b);
        return b;
    }
}

This code is not right because you expect it to throw an Exception and rollback. What should be done to have the transaction rolled back ? Either you use Bean Managed Transaction (BMT) or get the container involved in your local call. This is done by calling the createsB() method through the local interface (6). Thanks to the SessionContext you can get the Local interface of the EJB and call the method :

@Stateless
public class ABean implements ARemote, ALocal {

    @PersistenceContext private EntityManager em;
    @Resource private SessionContext sessionContext;

    public void createsA() {
        // calls the method through the local interface
        ALocal aLocal = sessionContext.getBusinessObject(ALocal.class);
        B b = aLocal.createsB(); (6)
        A a = new A(b);
        em.persist(a);
    }

    @TransactionAttribute(TransactionAttributeType.NEVER)
    public B createsB() {
        B b = new B();
        em.persist(b);
        return b;
    }
}

This will work has expected : the call to createsB() method will throw an exception and rollback any changes. This is a simple example but here is the way it works. You can send JMS messages in the middle, persist more data, update other… you will get the same behavior. JMS Messages will be rolled back and not send to the Queue or Topic if you remember the container.

Leave a Reply