即使提供了@EnableTransactionManagement,@Transactional 注释也不会回滚 RuntimeException

@Transactional annotation does not rollback RuntimeException even if @EnableTransactionManagement is provided

我有以下应用程序设置:

@SpringBootApplication
@EnableTransactionManagement
public class MyApp extends SpringBootServletInitializer {
    ...
}

带有一个 class,它具有以下内容:

public class DoStaff {

    public void doStaffOnAll(List<MyObject> myObjects) {
        for (int i=0; i<myObjects.size(); i++) {
            try {
                doStaffOnSingle(myObjects.get(i), i);
            } catch (Exception e) {
                e.printStrackTrace();
            }
        }
    }

    @Transactional
    public void doStaffOnSingle(MyObject myObject, int i) {
        repository.save(myObject);
        if (i%2==0) {
            throw new RuntimeException();
        }
    }
    
}

因此,如果我用 MyObject 的列表调用 DoStaff.doStaffOnAll,代码会保存列表中的所有元素,但还会为每个第二个元素抛出 运行time 异常。

由于 doStaffOnSingle@Transactional 注释,我预计每隔一个元素都会回滚。 但是如果我 运行 这段代码,每个元素都会成功保存在数据库中。这是为什么?我做错了什么?

引用 Spring Documentation:

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, @PostConstruct).

doStaffOnAll() 移动到不同的 Spring 组件,它将起作用。

或更改为aspectj模式。

我建议移动方法,并设计代码以使事务边界清晰明了,即 class 上的所有 public 方法都启动一个事务,或者 class 开始交易。

您的交易边界在哪里应该始终非常清楚,例如在分层设计中,您通常会使 @Service 层也成为事务层,即从更高层到服务层的任何调用都是原子事务。

@Transactional 注释之所以能够发挥作用,是因为有一个代理对象。

由于您直接调用该方法,因此您不会获得那种魔力。在 doStaffOnAll 方法中,您直接调用 doStaffOnSingle 方法。因此,没有添加任何交易行为。

尝试使用自调用来调用该方法。

@Service
public class DoStaff {
    @Autowired
    private DoStaff doStaff;

    public void doStaffOnAll(List<MyObject> myObjects) {
        for (int i=0; i<myObjects.size(); i++) {
           doStaff.doStaffOnSingle(..) // invoke like this
        }
    }

    @Transactional
    public void doStaffOnSingle(MyObject myObject, int i) {
       
    }
}

Since the doStaffOnSingle has @Transactional annotation, I would expect that every second element will be rolled back.

默认事务模式将提交所有内容或不提交任何内容。我想你会想要使用 REQUIRES_NEW 传播。

在此处查看支持的传播类型。

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Propagation.html#REQUIRED