Entities as Local Interface Parameters Can Harm You

Let’s start with a short quiz. TxMergeBean is a SLSB that uses CMT and EntityManager. Assume the following local and remote business interfaces:

public interface TxMergeCommon {
    void methodA();
    void methodB(MeineEntity entity);
}

@Local
public interface TxMergeLocal {}

@Remote
public interface TxMergeRemote {} 

and the following SLSB:

@Stateless
public class TxMergeBean implements TxMergeRemote, TxMergeLocal {

    @PersistenceContext
    private EntityManager em;

    @EJB
    private TxMergeLocal self;

    @Resource
    private SessionContext sctx;

    public void methodA() {
        MeineEntity entity = new MeineEntity("methodA");
        em.persist(entity);

        self.methodB(entity);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void methodB(final MeineEntity entity) {
        entity.setName("methodB");
        em.merge(entity);

        sctx.setRollbackOnly();
    }
}

Assume that MeineEntity is properly defined and its only constructor parameter is saved as ‘name’ attribute.

What will be the database persisted value of MeinEntity ‘name’ attribute after TxMergeBean#methodA() invocation?

  1. “methodA”,
  2. “methodB”,
  3. PersistenceException will be thrown,
  4. undefined.

Explanation

Let’s figure it out step by step.

We’re using CMT so the first relevant piece of information is that every change made to an entity in a managed state will be persisted during transaction commit.

Secondly, you should notice that methodB() executes in a separate transaction. Therefore, there is a need of em.merge(entity) invocation if we want to move from “unmanaged” to “managed” state. We’re using self-reference to pass a method call through EJB Container so @TransactionAttribute works fine – no hacks here.

Thirdly, the new transaction of methodB() is rolled back at the end. No changes made to our entity should be persisted at all (just like entity.setName("methodB") would never exist.)

Lastly, and what’s most interesting, is the usage of local business interface for self reference EJB. This means we’re using pass-by-reference parameters semantics (or pass-by-reference-copy to be more accurate) instead of pass-by-copy.
Now this is the root of all problems – can you see it already?

To visualize it, consider processing flow as below:

During this last operation, i.e. “Commit TX for methodA()” entity’ state will be flushed. But what actually is the state of our entity?

It’s “methodB”!

Despite the fact that TX for methodB() was rolled back we changed the state of MeineEntity passed as a parameter and because we were using local business interface (pass-by-reference) – this change will be reflected in the MeineEntity used in methodA().
Therefore, methodA() flushes its entity state which is “methodB”.

Solution

Would it look differently if we used remote business interface for self-reference? Yes it would. Simply changing this:

@EJB
private TxMergeLocal self;

into this:

@EJB
private TxMergeRemote self;

Would yield the correct result – MeineEntity with name attribute set to “methodA”.

That’s because the remote business interface operates on arguments with pass-by-value approach so our entity will be copied and passed to the methodB(). Therefore, any changes made within methodB() will not be reflected in the original entity.

Another way of dealing with this problem is not to use an entity passed as an argument to methodB() but the one being a result of a merge operation. Such entity is a copy of the original one, so you can fiddle with it as much as you want without harming the outside state.

We’re talking about changing this:

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
public void methodB(final MeineEntity entity) { 
    entity.setName("methodB");

    em.merge(entity);
    ...
}

into this:

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
public void methodB(final MeineEntity entity) { 
    MeineEntity editedEntity = em.merge(entity); 

    editedEntity.setName("methodB");

    ...
}

It seems that some people are thinking that even using JTA (CMT/BMT) the ‘merge’ operation is actually flushing some changes to the database. With such perception, they refuse to invoke merge(-) before changing entity state.

However, the fact is that merge(-) is just creating a copy of an entity that will be in a managed state within a given persistence context. Knowing that every change made to the current persistence context managed entity will be flushed during transaction commit allows you to firstly merge and then change the state of your entity.

In our case this would also allows us to be independent on the used interface – it will work with both local and remote EJB self references.