Spring 嵌套事务

Spring nested transactions

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

@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 正确执行此操作并允许嵌套事务?

如果从相同 class 的某些方法调用,Spring 事务基础结构将不会考虑您在 update 方法中的事务注释。如需更多了解 Spring 交易基础设施的工作原理,请参阅 this

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

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, will 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 behaviour so you should not rely on this feature in your initialization code, i.e. @PostConstruct.

但是,有一个选项可以切换到 AspectJ 模式

您的问题是在同一 proxy.It 的自调用中从另一个方法调用一个方法。 在您的情况下,您可以轻松修复它而无需在另一个服务中移动方法(为什么您需要创建另一个服务只是为了将某些方法从一个服务移动到另一个服务以避免自我调用?),只是为了调用第二种方法不是直接来自当前 class,而是来自 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); 容器 returns 代理对象时,您可以在此代理上调用 methodTwo(...) 事务。

关于嵌套事务的基本经验法则是它们完全依赖于底层数据库,即支持嵌套事务并且它们的处理依赖于数据库并随数据库而变化。 在某些数据库中,嵌套事务所做的更改在提交嵌套事务之前不会被 'host' 事务看到。这可以使用 @Transactional (isolation = "")

中的事务隔离来实现

您需要确定代码中抛出异常的位置,即从父方法:"validateBoard" 或从子方法:"update".

您的代码片段显示您正在明确抛出异常。

你必须知道::

In its default configuration, Spring Framework’s transaction infrastructure code only marks a transaction for rollback in the case of runtime, unchecked exceptions; that is when the thrown exception is an instance or subclass of RuntimeException.

但是@Transactional 从不回滚任何已检查异常的事务。

因此,Spring 允许您定义

  • 应回滚事务的异常
  • 不应回滚事务的异常

尝试注释您的子方法:使用@Transactional(no-rollback-for="ExceptionName") 或您的父方法进行更新。

使用“自我”注入模式可以解决此问题。

示例代码如下:

@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”。

  • 您可以创建一个新服务 (CustomTransactionalService),它将 运行 您的代码用于新事务 :

    @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();
        }
    } 
    
  • 然后:

    @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(() -> {
              // ...
          });
       }
    }