Spring嵌套事务

ale*_*oid 24 spring spring-transactions spring-data spring-data-jpa spring-boot

在我的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;
}
Run Code Online (Sandbox Code Playgroud)

在这个方法里面,我使用另一种叫做的服务方法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);
}
Run Code Online (Sandbox Code Playgroud)

我需要update独立于validateBoard方法中启动的所有者事务,在方法中提交对数据库的更改.现在任何变化都会在任何异常的情况下回滚.

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

如何使用Spring正确执行此操作并允许嵌套事务?

Jak*_*bro 26

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

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

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


xyz*_*xyz 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) {
    .......
   }
}
Run Code Online (Sandbox Code Playgroud)

当您调用context.getBean(SomeService.class).methodTwo(object);容器时,它会返回代理对象,并且在此代理上您可以methodTwo(...)使用事务进行调用。


she*_*997 6

使用“自我” jnject合作伙伴可以解决此问题。

示例代码如下:

@Service @Transactional
public class YourService {
   ... your member

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

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

   public void methodBar() {
      //call self.methodFoo() rather than this.methodFoo()
      self.methodFoo();
   }
}
Run Code Online (Sandbox Code Playgroud)

关键是使用“自我”而不是“这个”。

  • Spring 不会以任何方式抱怨“循环依赖”吗?对我来说,自引用看起来像是一种循环依赖,就像 A -> B -> A 一样。 (2认同)

Phi*_*lip 5

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

\n\n

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

\n\n

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

\n\n

你必须知道::

\n\n
\n

在其默认配置中,Spring Framework\n\xe2\x80\x99s 事务\n 基础架构代码仅在运行时、未检查异常的情况下标记要回滚的事务;即当抛出的异常是 RuntimeException 的实例或子类时。

\n
\n\n

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

\n\n

因此,Spring 允许您定义

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

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

\n