以下示例显示了本质:一个进程EJB在其自己的TN中调用审计器,在开始时执行DB操作但确定失败,设置回滚,然后在完成时调用审计器。审计器EJB调用JPA在单独的事务(REQUIRES_NEW)中创建审核记录,当事务成功时,这个过程很好地运行。代码显示了一个通用的审计结构,省略了所有不必要的代码--泛型似乎是问题的一部分。
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);
}
}
问题在于当调用parent.method1()时,它会静默执行,直到确定失败并设置回滚。此时,第二个审计调用抛出"客户端事务已中止"的异常,就好像它是当前事务的一部分而不是单独的事务。此外,即使成功,第一个审计调用也不会将任何数据放入数据库--直到父级提交才会提交(这正常吗?)应该使用非XA在单独的事务中进行。
我读过很多文章声称事务是独立的,但this one表明嵌套事务直到父级提交才会提交,并且如果父级回滚,子级也会回滚。如果这是真的,我不知道如何提交部分或状态工作,因为直到最顶层事务提交之前,没有东西会提交。
JPA配置--我最初使用了一个数据源,但后来切换到两个数据源,但我没有观察到任何差异。
<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>
有人能否说一下回滚是应该这样工作的吗?也许在XA下会有所不同?
我已上传了此带注释的事务日志,请查找代码中控制点的条目。
添加:这个第二个日志具有最详细的EclipseLink级别。
编辑:我已经修改了代码以显示导致问题的确切原因,并返回到一个持久性管理器。