I'm having a problem with transaction rollback in which a sub-transaction annotated with REQUIRES_NEW is being rolled back because the parent transaction was rolled back. I am puzzled because I had thought that JTA/JPA treated these transactions independently so that rollback on one did not affect the other. I'm using Java 1.6.0_24, EJB 3.1, GlassFish 3.0.1, JPA 2, MS SQL Server (non-XA) 2008, CMT.
The example below shows the essence: A Process EJB in its own TN invokes the auditor at its start, performs DB actions but determines failure, sets rollback, and then invokes auditor upon completion. An Auditor EJB invokes JPA to create an audit record in a separate transaction (REQUIRES_NEW) and this works fine when the transaction is successful. The code shows a generic auditing structure with all unnecessary code omitted--the generics appear to be part of the problem.
public interface ErrorDescriptor {
public String getName ();
}
public interface GenericAuditor<T extends ErrorDescriptor> {
public void log(T errorType);
}
public abstract class AbstractGenericAuditorImpl<T extends ErrorDescriptor>
implements GenericAuditor<T> {
}
public enum AuditType implements ErrorDescriptor {
BEGIN, FAILED;
public String getName() { return name(); }
}
public interface Auditor extends GenericAuditor<AuditType> {
// The absence of the following 2 lines causes the problem
@Override
public void log(AuditType errorType);
}
@Stateless
public class AuditorImpl
extends AbstractGenericAuditorImpl<AuditType>
implements Auditor {
@PersistenceContext ("EntityPersistenceManagement")
protected EntityManager entityManager;
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void log (AuditType e) {
ErrorEvent errorEvent = new ErrorEvent (); // entity
errorEvent.setName (e.getName());
entityManager.persist (errorEvent);
}
}
@Stateless
public class ProcessImpl implements Process {
@Resource private EJBContext ejbContext;
@EJB private Auditor auditor;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void method1() {
auditor.log(AuditType.BEGIN);
// Perform series of actions including database ops
// Takes up to 1 minute
// Somethings happen that detects failure
ejbContext.setRollbackOnly ();
auditor.log(AuditType.FAILED);
}
}
The issue is that when parent.method1() is invoked it proceeds quietly until it determines failure and sets rollback. At that point the 2nd audit call throws Client's Transaction Aborted as if it were part of the current transaction rather than in a separate transaction. Moreover, the first audit call puts no data in the database--even when successful it does not commit until the parent commits (is that normal?) This should be non-XA in separate transactions.
I've read many articles that claim the transactions are independent, but this one suggests that nested transactions do not commit until the parent commits and that if the parent rollsback, the children rollback also. I don't see how I could commit partial or status work if that were true because nothing would commit until the top most transaction committed.
JPA Config--I originally used a single data source but later switched to two but I observed no differences.
<persistence-unit name="EntityPersistenceManagement" transaction-type="JTA">
<jta-data-source>jdbc/app1</jta-data-source>
<properties>
<property name="eclipselink.logging.level" value="INFO"/>
<property name="eclipselink.weaving.internal" value="false"/>
<property name="eclipselink.weaving" value="false"/>
<property name="eclipselink.weaving.fetchgroups" value="false"/>
<property name="eclipselink.weaving.changetracking" value="false"/>
<property name="eclipselink.weaving.lazy" value="false"/>
<property name="eclipselink.weaving.internal" value="false"/>
<property name="eclipselink.weaving.eager" value="false"/>
<property name="eclipselink.cache.shared.default" value="false"/>
<property name="eclipselink.cache.shared" value = "false"/>
<property name="eclipselink.query-results-cache" value="false"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect"/>
</properties>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<class>org.foo.entities.AppRecord</class>
<class>org.foo.entities.ErrorEvent</class>
Can anyone say if that's how rollback is supposed to work? Perhaps this is different under XA?
I have uploaded this annotated transaction log look for ** entries for control points within the code.
ADDED: This second log has EclipseLink level at FINEST.
EDIT: I have revised the code to show exactly what causes the problem and returned to only one persistence manager.