EJB JTA/JPA CMT事务回滚会影响子事务。

3
我在事务回滚方面遇到了问题,其中一个使用REQUIRES_NEW注释的子事务被回滚,因为父事务被回滚。这让我感到困惑,因为我曾认为JTA/JPA会独立处理这些事务,因此一个事务的回滚不会影响另一个事务。我正在使用Java 1.6.0_24、EJB 3.1、GlassFish 3.0.1、JPA 2、MS SQL Server(非XA)2008和CMT。
以下示例显示了本质:一个进程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级别。

编辑:我已经修改了代码以显示导致问题的确切原因,并返回到一个持久性管理器。


你发的片段是正确的,应该可以按照你所描述的方式工作。你能否给我们提供JPA配置(以检查您是否指定了正确的JTA配置)? - Carlo Pellegrini
谢谢您的回复 - 您是否确认嵌套事务不是独立的(父回滚导致子回滚)?我不明白这如何与EJB 3.1规范的13.6.2.4相一致。我一直在阅读JTA / JPA不支持嵌套事务(仅支持平面事务),但它们确实存在。 - Matt Humphrey
JTA/JPA不支持嵌套事务与你的问题和解决方案无关。当你使用REQUIRES_NEW事务属性调用EJB方法时,必须创建一个全新的事务,它完全独立于“原始”事务。它们不会被嵌套。 - Csaba
@Csaba 非常好,我更喜欢 EJB 在没有嵌套事务的情况下工作,但您能帮助我理解为什么所谓的完全独立事务在父事务回滚时也会回滚吗? - Matt Humphrey
我更新了代码,以反映最接近的简化,并且可以可靠地引起/修复问题。当审计员接口中的日志声明不存在时,不会创建新事务,并且提交的子事务将回滚。但是当声明存在时,代码正常工作,REQUIRES_NEW通过创建新事务来实现,子事务不会回滚。该声明应该是不必要的,因为它完全由具体的泛型参数隐含。 - Matt Humphrey
显示剩余2条评论
5个回答

4

+1 找到了这个错误报告!我一直知道泛型是邪恶的 ;) - ewernli

1
我认为我遇到了类似的问题(有点类似的情况)。在内部事务中,我更新了数据库,但当父事务结束时,我遇到了一个OptimisticLockException,告诉我记录已经被更新过了。
从EclipeLink文档中了解到UnitOfWorks后,我认为(至少它起作用了=)解决了这个问题。问题是父UnitOfWork不知道我在RequiresNew事务中所做的更改。并且在父事务成功结束时,“Parent”UnitOfWork包含了UPDATE的更改。 因此,在RequiresNew调用之后,我添加了。内部事务的结果-应用。父事务-成功结束。
Matt,你写道:
引用:

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.

也许em.refresh()可以解决这个问题?也许你的父UnitOfWork会将db回滚到初始状态。
Matt,感谢你提供的大量参考资料。

0

它们应该是独立的。可能是某个地方出现了错误,请在finest上包含失败交易的日志。


添加主问题的链接——日志非常大。 - Matt Humphrey
我指的是EclipseLink finest级别的日志,你的日志中没有包含任何EclipseLink日志。 - James
日志只显示了一个“Created new JavaEETransactionImpl”,因此看起来Glassfish没有创建两个事务。请确保您已经正确编译/部署了您的代码。 - James
卸载应用程序,重启GlassFish(清除OSGI等)是必要的测试技术,但我已尝试了许多不同的测试仍然没有成功。我的实际审计员类继承自抽象实用(EAO)超类,在其他应用程序中正常工作,因此我怀疑声明中有一些微妙的差异影响了事务。REQUIRES_NEW 被忽略了,所有操作都发生在一个事务中。 - Matt Humphrey

0

看起来你的进程正在全局事务(分布式)中运行,因此,如果一个事务失败,其余事务将不会提交。(就像你所描述的行为)

你写道:

"JPA配置-我最初使用了单个数据源,但后来切换到两个,但我没有观察到任何差异。"

对我来说,这就是问题所在:当你添加一个新资源时,事务管理器自动假定现在需要分布式事务。

"请注意,2PC对于单个资源没有增加价值-仅适用于跨多个资源的事务。高效的事务管理器将退回到简单的1PC,如果只涉及单个资源,则尽管这仅仅是J2EE规范建议而非要求"

来自书籍Expert One on One J2EE Development without EJB p. 233

我读了你最后一篇日志,对我来说很清楚,事务是全局的,因此,你应该尝试改变这种行为。

我不知道你的框架实际上是如何工作的,但我对日志中出现的一个类很好奇:

com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate

我看到它实现了com.sun.enterprise.transaction.spi.JavaEETransactionManagerDelegate接口。

根据源代码的注释

支持使用JTS进行XA事务的JavaEETransactionManagerDelegate的实现。

public class JavaEETransactionManagerJTSDelegate

仅支持使用单个非XA资源进行本地事务的JavaEETransactionManagerDelegate的实现。

public class JavaEETransactionManagerSimplifiedDelegate

我不知道如何配置您的框架,但似乎您需要告诉它在您的情况下必须使用其他实现(JavaEETransactionManagerSimplifiedDelegate)。


感谢您的建议。我最初是在单个非-XA数据源下以及在MySQL下观察到了这个问题,但我会重新测试该条件以确认。 - Matt Humphrey
重新测试具有相同的行为,事务日志显示所有事务具有相同的ID。 - Matt Humphrey
我试图发表评论,但太长了,所以我修改了答案。 - Gabriel Aramburu
我认为你的解释是正确的,但我已经将事务及其父事务标记为REQUIRES_NEW。我在GlassFish 3.0.1中使用纯EJB 3.1,因此我没有进行任何特殊配置事务管理器的操作。我已经在问题中添加了更多信息。 - Matt Humphrey

0

我找到了一堆参考资料,它们共同暗示尽管 EJB 不支持嵌套事务,但 EclipseLink(GlassFish)支持,这可能至少能解释问题。

EJB 3.1 规范的第13.1.2节清楚地说明事务是平面结构,没有嵌套事务。第15.6.1.2节说明符合规范的容器不得使用嵌套事务。第13.6.2.4节说明 REQUIRES_NEW 会挂起当前事务,并在第二个事务提交后恢复。

JPA/JTA WikiBooks中的“嵌套事务”再次说明不支持嵌套事务,然后给出了一个全面的定义,与我看到的行为相匹配。

JTA 1.0.1规范第3.1节指出嵌套事务不是必需的,这可能意味着它们是可能的。

EclipseLink维基条目显示它们支持嵌套“工作单元”,其中嵌套提交不是独立的,而是延迟到父提交。这实际上与我看到的行为相匹配。

因此,如果我得到嵌套事务,那么如何创建隔离的、明确的提交以进行审计并避免在父事务回滚时回滚也就不太清楚了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接