【问题标题】:Spring nested transactionsSpring嵌套事务
【发布时间】:2016-09-10 01:38:10
【问题描述】:

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

【问题讨论】:

  • 显然您正在调用同一类中的方法,因此 Spring 无法拦截调用并应用事务代理(REQUIRES_NEW 传播被忽略)。您应该将 update 方法迁移到另一个 Spring bean,
  • 谢谢,现在一切正常

标签: spring spring-boot spring-data spring-data-jpa spring-transactions


【解决方案1】:

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

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

但是,可以选择切换到 AspectJ 模式

【讨论】:

    【解决方案2】:

    使用“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”。

    【讨论】:

    • Spring 不会以任何方式抱怨“循环依赖”吗?自引用对我来说就像一个循环依赖,就像 A -> B -> A 是一个。
    • 此代码无法解决问题,因为如果 methodFoo 不起作用,像您所做的嵌套事务将不会回滚,在这种情况下您将在系统中创建功能问题
    【解决方案3】:

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

    您需要确定代码中引发异常的位置,即来自父方法:“validateBoard”或来自子方法:“update”。

    您的代码 sn-p 表明您正在明确抛出异常。

    你必须知道::

    在其默认配置中,Spring Framework 的事务 基础设施代码仅在情况下标记事务以进行回滚 运行时,未经检查的异常;那就是抛出的异常是 RuntimeException 的实例或子类。

    但@Transactional 永远不会为任何已检查的异常回滚事务。

    因此,Spring 允许您定义

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

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

    【讨论】:

      【解决方案4】:

      update 方法中的事务注释如果从同一类的某个方法调用,Spring 事务基础架构将不会考虑。有关 Spring 事务基础架构如何工作的更多了解,请参阅this

      【讨论】:

        【解决方案5】:

        您的问题是从同一代理内的另一个方法调用方法。它是自调用。 在您的情况下,您可以轻松修复它而无需在另一个服务中移动方法(为什么您需要创建另一个服务只是为了将某些方法从一个服务移动到另一个只是为了避免自我调用?),只是为了调用第二种方法不是直接来自当前类,而是来自弹簧容器。在这种情况下,您使用事务而不是自调用调用代理第二种方法。

        当您需要自调用时,此原则对任何代理对象都很有用,而不仅仅是事务代理。

        @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(...)

        【讨论】:

          【解决方案6】:
          • 您可以创建一个新服务 (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(() -> {
                      // ...
                  });
               }
            }
            

          【讨论】:

            猜你喜欢
            • 2019-07-12
            • 2012-10-20
            • 2021-11-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-12-29
            • 2011-11-01
            • 1970-01-01
            相关资源
            最近更新 更多