Matt Humphrey
Matt Humphrey

Reputation: 71

EJB JTA/JPA CMT transaction rollback affects child transactions

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.

Upvotes: 3

Views: 2742

Answers (5)

user3663929
user3663929

Reputation: 11

I think I have quite similar issue(with kind of case). In inner transaction I had an update in db, but when parent transaction was finishing, I had an OptimisticLockException, telling the record had already been updated.

Knowing about UnitOfWorks from EclipeLink doc, I think(at least it works=) have solved the issue. The problem was that parent UnitOfWork did not know about changes I'd made in RequiresNew transaction. And in the successfull end of parent transaction 'Parent' UnitOfWork contained change for UPDATE. So I have added em.refresh() right after RequiresNew call. Results of inner transaction - applied. Parent transaction - successfully finishing.

Matt, you wrote:

So if I am getting nested transactions, it's not clear how to create isolated, definate commits for auditing that will not rollback when the parent rollsback.

Maybe em.refresh() would solve the issue? Maybe your parent UnitOfWork rollback db to the start state.

Matt, thanks for bunch of references.

Upvotes: 1

Matt Humphrey
Matt Humphrey

Reputation: 71

This issue is a bug with GlassFish https://java.net/jira/browse/GLASSFISH-8319, documented as resolved in v 3.1.

Upvotes: 4

Gabriel Aramburu
Gabriel Aramburu

Reputation: 2981

It seems to be that your process is running within a global transaction (distributed), therefore, if one transaction fails the rest of them will not commit. (like the behavior that you are describing)

You wrote:

"JPA Config--I originally used a single data source but later switched to two but I observed no differences."

For me this is the problem: when you add a new resource the Transaction Manager automatically assumes that now the transactions need to be distributed.

"... note that 2-Phase-Commit does not add value for single resources- only for transactions spanning multiple resources. An efficient transaction manager will fall back to simple 1-Phase-Commit if touching only a single resource, although this is merely suggested rather than required by the J2EE specification"

From book Expert One on One J2EE Development without EJB p. 233

I was reading you last log, to me it is clear that the transaction is global, therefore, you should try to change this behavior.

I don't know how your framework actually works, but I was curious about a class that appear in the log:

com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate

I saw that it implements the com.sun.enterprise.transaction.spi.JavaEETransactionManagerDelegate interface.

According to the source code's comments:

Implementation of JavaEETransactionManagerDelegate that supports XA transactions with JTS.

public class JavaEETransactionManagerJTSDelegate

Implementation of JavaEETransactionManagerDelegate that supports only local transactions with a single non-XA resource.

public class JavaEETransactionManagerSimplifiedDelegate

I don't know how to configure you framework, but it seems to be that you need to tell him that other implementation (JavaEETransactionManagerSimplifiedDelegate) must be used for your case.

Upvotes: 0

Matt Humphrey
Matt Humphrey

Reputation: 71

I have found a bunch of references that together imply that although EJB does not supported nested transactions, EclipseLink (GlassFish) does, which may at least explain the problem.

EJB 3.1 Spec section 13.1.2 clearly states transactions are flat and there are no nested transactions. Section 15.6.1.2 says compliant containers must not use nested transactions. Section 13.6.2.4 says REQUIRES_NEW will suspend the current transaction and resume after the 2nd transaction commits.

JPA/JTA WikiBooks in the "Nested Transactions" states again they are not supported and then gives a comprehensive definition of them that matches the behavior I am seeing.

The JTA 1.0.1 Spec section 3.1 says that nested transactions are not required which may imply they are possible.

But this EclipseLink wiki entry shows that they DO support nested "units of work" in which nested commits are not independent but deferred until the parent commit. This actually matches the behavior I am seeing.

So if I am getting nested transactions, it's not clear how to create isolated, definate commits for auditing that will not rollback when the parent rollsback.

Upvotes: 0

James
James

Reputation: 18379

They should be independent. It could be a bug somewhere, can you please include the log of the failed transaction on finest.

Upvotes: 0

Related Questions