Spring嵌套事务

45

在我的Spring Boot项目中,我实现了以下服务方法:

@Transactional
public boolean validateBoard(Board board) {
    boolean result = false;
    if (inProgress(board)) {
        if (!canPlayWithCurrentBoard(board)) {
            update(board, new Date(), Board.AFK);
            throw new InvalidStateException(ErrorMessage.BOARD_TIMEOUT_REACHED);
        }
        if (!canSelectCards(board)) {
            update(board, new Date(), Board.COMPLETED);
            throw new InvalidStateException(ErrorMessage.ALL_BOARD_CARDS_ALREADY_SELECTED);
        }
        result = true;
    }
    return result;
}

在这个方法内,我使用另一个服务方法叫做update:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public Board update(Board board, Date finishedDate, Integer status) {
    board.setStatus(status);
    board.setFinishedDate(finishedDate);

    return boardRepository.save(board);
}

我需要在update方法中独立于validateBoard方法中启动的所有者事务提交更改到数据库。目前,如果出现任何异常,任何更改都会回滚。

即使使用@Transactional(propagation = Propagation.REQUIRES_NEW)也不起作用。

如何在Spring中正确实现此操作并允许嵌套事务?


23
显然,您正在同一类中调用一个方法,因此Spring无法拦截调用并应用事务代理(REQUIRES_NEW传播被忽略)。您应该将update方法迁移到另一个Spring bean中。 - Ori Dar
1
谢谢,现在一切都按预期工作。 - alexanoid
6个回答

47
这份文档涵盖了您的问题 - https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#transaction-declarative-annotations

在代理模式下(默认情况),只会拦截通过代理传入的外部方法调用。这意味着自我调用,实际上是目标对象内部调用另一个方法,即使被调用的方法标有 @Transactional 注解,在运行时也不会触发实际的事务。此外,为了提供预期的行为,代理必须完全初始化,因此您不应该在初始化代码(例如 @PostConstruct)中依赖此功能。

然而,有一个选项可以切换到 AspectJ 模式。


8
使用"self"注入模式可以解决这个问题。
以下是示例代码:
@Service @Transactional
public class YourService {
   //... your member

   @Autowired
   private YourService self;   //inject proxy as an instance member variable ;

   @Transactional(propagation= Propagation.REQUIRES_NEW)
   public void methodFoo() {
      //...
   }

   public void methodBar() {
      //call self.methodFoo() rather than this.methodFoo()
      self.methodFoo();
   }
}

重点是使用“self”而不是“this”。

2
Spring会不会抱怨“循环依赖”呢?自我引用看起来像是一种循环依赖,就像A->B->A一样。 - Snackoverflow
这段代码无法解决问题,因为像您所做的嵌套事务,在 methodFoo 不起作用的情况下不会回滚,这样会在系统中创建功能问题。 - Amine Barrak
@Snackoverflow:仅适用于基于构造函数的注入。 - Nikolas Charalambidis

6
你的问题是在同一个代理中从另一个方法调用方法。这是自我调用。在你的情况下,你可以很容易地修复它,而不需要将一个方法移动到另一个服务中(为什么你需要创建另一个服务只是为了避免自我调用而将某些方法从一个服务移到另一个服务?),只需从spring容器中直接调用第二个方法。在这种情况下,你使用事务代理调用第二个方法而不是自我调用。
当你需要自我调用时,这个原则对于任何代理对象都很有用,不仅仅是事务代理。
@Service
class SomeService ..... {
    -->> @Autorired
    -->> private ApplicationContext context;
    -->> //or with implementing ApplicationContextAware

    @Transactional(any propagation , it's not important in this case)
    public boolean methodOne(SomeObject object) {
      .......
       -->> here you get a proxy from context and call a method from this proxy
       -->>context.getBean(SomeService.class).
            methodTwo(object);
      ......
   }

    @Transactional(any propagation , it's not important in this case)public boolean 
    methodTwo(SomeObject object) {
    .......
   }
}

当您调用 context.getBean(SomeService.class).methodTwo(object); 时,容器将返回代理对象,您可以在该代理对象上使用事务调用 methodTwo(...)


5
关于嵌套事务的基本规则是它们完全依赖基础数据库,即支持嵌套事务及其处理是数据库相关的并因数据库而异。在某些数据库中,除非提交嵌套事务,否则“主”事务看不到嵌套事务所做的更改。这可以通过@Transactional(isolation =“”)中的Transaction隔离来实现。
您需要确定代码中引发异常的位置,即父方法:“validateBoard”或子方法:“update”。
您的代码片段显示您明确地抛出异常。
您必须知道:在默认配置中,Spring Framework的事务基础设施代码仅在运行时未经检查的异常情况下标记事务回滚;即抛出的异常是RuntimeException的实例或子类时。但@Transactional从不为任何经过检查的异常回滚事务。
因此,Spring允许您定义:
- 应该回滚事务的异常 - 不应回滚事务的异常
尝试使用@Transactional(no-rollback-for="ExceptionName")对您的子方法update或父方法进行注释。

2

如果从同一类的某个方法调用update方法,则Spring事务基础设施将不会考虑您在update方法中的事务注释。要更好地理解Spring事务基础结构的工作原理,请参阅此文


1
  • You could create a new service (CustomTransactionalService) that will run your code in a new transaction :

    @Service
    public class CustomTransactionalService {
    
        @Transactional(propagation= Propagation.REQUIRES_NEW)
        public <U> U runInNewTransaction(final Supplier<U> supplier) {
            return supplier.get();
        }
    
        @Transactional(propagation= Propagation.REQUIRES_NEW)
        public void runInNewTransaction(final Runnable runnable) {
            runnable.run();
        }
    } 
    
  • And then :

    @Service
    public class YourService {
    
       @Autowired
       private CustomTransactionalService customTransactionalService;  
    
       @Transactional
       public boolean validateBoard(Board board) {
          // ...
       }
    
       public Board update(Board board, Date finishedDate, Integer status) {
          this.customTransactionalService.runInNewTransaction(() -> {
              // ...
          });
       }
    }
    

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