在子方法发生异常时,只针对事务进行回滚,而不是全局回滚。

6

我在我的应用程序中使用Hibernate + Spring + @Transactional 注解来处理事务。

事务管理器声明如下:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

这在大多数情况下都很有效,但我发现一个问题,当我有2个方法,它们都用@Transactional进行了注释:
package temp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

public class OuterTestService {
    @Autowired
    private InnerTestService innerTestService;

    @Transactional
    public void outerMethod() {
        try {
            innerTestService.innerTestMethod();
        } catch (RuntimeException e) {
            // some code here
        }
    }
}

and

package temp;

import org.springframework.transaction.annotation.Transactional;

public class InnerTestService {

    @Transactional
    public void innerTestMethod() throws RuntimeException {
        throw new RuntimeException();
    }
}

当我调用OuterTestService#outerMethod()时,我遇到了异常。
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

由于只有一个事务(没有嵌套事务),整个outerTestMethod()的事务被标记为仅回滚。

我发现可以使用noRollbackFor轻松克服这个问题:

package cz.csas.pdb.be.service.tempdelete;

import org.springframework.transaction.annotation.Transactional;

public class InnerTestService {

    @Transactional(noRollbackFor = RuntimeException.class)
    public void innerTestMethod() throws RuntimeException {
        throw new RuntimeException();
    }
}

但是每个方法都需要明确使用它。由于在测试期间(这些测试是回滚的)不会引发此错误,因此这是不可接受的。

我的问题是-是否有一种自动设置事务仅在从启动事务的方法(在本例中为outerTestMethod())引发异常时回滚的方式(例如,不显式为每种方法设置)?


不确定你期望什么,可以查看这个链接(但我认为这不是一个好主意):http://static.springsource.org/spring/docs/2.0.0/api/org/springframework/transaction/support/AbstractPlatformTransactionManager.html#setGlobalRollbackOnParticipationFailure(boolean) - Dhananjay
我希望能够配置事务管理器,使其在发生异常时不会回滚(或标记为仅回滚),除非它发生在启动(或导致启动)事务的方法中。 - Ondrej Skalicka
在一个@Transactional方法中调用另一个方法似乎对我来说不是好消息,就好像你并不真正清楚事务的范围是什么。我会先解决这个问题,无论如何。 - Donal Fellows
好的,我需要在两个方法上都加上 @Transactional,因为每个方法都可以直接调用,而且我需要它们在同一个事务中。 - Ondrej Skalicka
2个回答

11

创建另一个注解,例如@NoRollbackTransactional,类似于:

@Transactional(noRollbackFor = RuntimeException.class)
public @interface NoRollbackTransactional {
}

并在您的方法中使用它。另一方面,我同意Donal的评论,您应该修订您的事务范围,我认为从另一个@Transactional调用@Transactional通常不是一个好主意。


1
你是说隔离级别,即PROPAGATION_REQUIRED(通常为默认值)不受支持吗? - JoeG
4
一个 @Transactional 调用另一个 @Transactional 时,事务通常应该从一个传递到另一个(传播),而不会产生不良影响。当然,您将失去“noRollbackFor”的功效。似乎 Spring Data JPA 经常这样做。 - JoeG
关于“通常不建议从另一个@Transactional调用@Transactional”,我同意JoeG的观点。除了调用堆栈中的小开销之外,我不确定问题是什么。如果您可以不嵌套就好了。 - Antonio

5

您可能还需要关闭globalRollbackOnParticipationFailure

当我在包围事务中有一个独立事务时,我遇到了这个问题。当独立事务失败时,它也会导致包围事务失败。

对于我来说,关闭参与标志解决了这个问题:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="globalRollbackOnParticipationFailure" value="false" />
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

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