Hibernate: Avoiding a “fake” exception: javax.persistence.TransactionRequiredException: Executing an update/delete query

By | September 12, 2016

I encountered some weird issue caused by the way Hibernate works. The exception messages returned by Hibernate are confusing as they are just a side-effect of the real issue.

The code in question was something like that:

In a manager bean we have the following:


@Stateless(name = "ObjectManagerBean", mappedName = "ObjectManagerBean/remote")
public class ObjectManagerBean
...
@PersistenceContext(unitName = "default")
protected EntityManager em;
...
public void approve(Auditor auditor, ManagedObject managed)
throws PersistenceException, ValidationException {
...
}
...

Then in the main class we have the following:

public class Test {
...
@PersistenceContext(unitName = "default")
protected EntityManager em;
...
void execute(){
...
try{
manager.setProcessed(true);
manager.approve(auditor,managed)
}catch(Exception e){
e.printStackTrace();
}
...
Query q = em.createNamedQuery("deleteProcessingStatusById");
q.setParameter("id", FPId);
q.executeUpdate();
}
...
}

Now in the case we have a persistence exception in the approve method the exception thrown is displayed but while executing the query we get


actionRequiredException: Executing an update/delete query
javax.ejb.EJBTransactionRolledbackException: nested exception is: javax.ejb.EJBException: See nested exception; nested exception is: javax.persistence.TransactionRequiredExceptio
n: Executing an update/delete query
at com.ibm.ejs.container.BusinessExceptionMappingStrategy.mapCSIException(BusinessExceptionMappingStrategy.java:167)
at com.ibm.ejs.container.BusinessExceptionMappingStrategy.mapCSITransactionRolledBackException(BusinessExceptionMappingStrategy.java:618)
at com.ibm.ejs.container.EJSDeployedSupport.mapCSITransactionRolledBackException(EJSDeployedSupport.java:700)
at com.ibm.ejs.container.EJSContainer.postInvokeRolledbackException(EJSContainer.java:4826)
at com.ibm.ejs.container.EJSContainer.postInvoke(EJSContainer.java:4577)
...

First reaction was to blame the query then the database, but after investigating the query and the database logs I found out that the query is correct and did not even reach the DB. The issue must be an application + hibernate issue.

The conclusion

It seems that even if you catch the PersistenceException the transaction started by the execute() method is interrupted. As a result when the call tries to execute the query by calling q.executeUpdate(), the EntityManager is no longer available so the update query fails not because of the DB or query syntax but because EntityManger is no longer available.

Solution

The solution is to isolate the approve method in a new transaction.

Change the Manager bean code and add a new method approveNewTX:


@Stateless(name = "ObjectManagerBean", mappedName = "ObjectManagerBean/remote")
public class ObjectManagerBean
...
@PersistenceContext(unitName = "default")
protected EntityManager em;
...

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void approveNewTX(Auditor auditor, ManagedObject managed)
throws PersistenceException, ValidationException {
managed = em.merge(managed);
approve(auditor, managed)
}
public void approve(Auditor auditor, ManagedObject managed)
throws PersistenceException, ValidationException {
...
}
...

Then in the main class we have the following change also:

public class Test {
...
@PersistenceContext(unitName = "default")
protected EntityManager em;
...
void execute(){
...
try{
manager.setProcessed(true);
manager.approveNewTX(auditor,managed);
em.refresh(managed);
}catch(Exception e){
e.printStackTrace();
}
...
Query q = em.createNamedQuery("deleteProcessingStatusById");
q.setParameter("id", FPId);
q.executeUpdate();
}
...
}

Note 1: We force a new transaction with @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW). This will ensure that if we have a PersistenceException in the approve method only the EntityManager of the sub-transaction will no longer be available. The EntityManager from the main transaction will be OK and the update query will execute.

Note 2:
Inside the approveNewTX method we force a merge “managed = em.merge(managed);” in order not to lose the changes that are done to the managed object before the new transaction is called. This will avoid also possible persistence merge exceptions caused by hibernate not being able to merge detached copies of the same object.

Note 3: After we return from the approveNewTX method we have to refresh the managed object with an em.refresh(managed). This will ensure that all the changes done to the managed object inside the new sub-transaction started by approveNewTX are visible in the main transaction.

Advertisements