【问题标题】:Transaction problem using REQUIRES_NEW in a for loop with Spring Boot在 Spring Boot 的 for 循环中使用 REQUIRES_NEW 的事务问题
【发布时间】:2020-05-05 09:47:55
【问题描述】:

我需要处理一个元素列表,如果发生异常(或 RuntimeException),它无法撤消之前完成的工作。它只能撤消该时间的数据库操作,并且必须继续处理其他元素。

我的策略是用 propagation = Propagation.REQUIRED 创建一个带有 for 循环的类,并在其中调用另一个方法 propagation = Propagation.REQUIRES_NEW.

Transaction 1->
    loop -> transaction 2
         -> transaction 3
         -> ...
         -> transaction N
end of transaction 1

在此策略中,如果事务 2 发生异常,则会回滚,事务 3 将正常继续。

问题:如果事务2发生异常,则不回滚,事务3正常继续。事务 1 不受影响。

如果我在 Service2 的 catch 块中添加 throw e 并且它在事务 3 中发生异常,它会被回滚并且事务 2 不受影响(到目前为止太好了),但是事务 1 收到异常并且进程停止,不处理剩余的元素。

我做错了什么? =/

代码:

package test.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DefaultController {

    @Autowired
    private Service1 service1;

    @ResponseBody
    @RequestMapping(method = RequestMethod.GET, path = "/")
    public ResponseEntity<?> test() throws Exception {
        service1.m1();
        return new ResponseEntity<>(HttpStatus.OK);
    }

}

这个控制器调用一个服务:

package test.controller;

import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class Service1 {

    @Autowired
    private Service2 service2;

    @Transactional(transactionManager = "transactionManager", propagation = Propagation.REQUIRED)
    public void m1() {

        List<Integer> list = Arrays.asList(1, 2);

        for (Integer j : list) {
            service2.m2(j);
            System.out.println("Exception for j = " + j);
        }
    }
}

这个service1调用了一个service2,因为我知道Spring Boot AOP是基于代理的,所以我需要另一个bean来切换我的事务的Propagation:

package test.controller;

import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import test.model.domain.Log;
import test.service.LogService;

@Service
public class Service2 {

    @Autowired
    private LogService logger;

    @Transactional(transactionManager = "transactionManager", propagation = Propagation.REQUIRES_NEW)
    public void m2(int i) {

        try {
            Log log = new Log();
            log.setDataMensagem(new Date());
            log.setDescricaoEnvioRecebimento("TEST");
            log.setDescricaoMensagem("TEST1");
            log.setIdMensagem("TEST2");
            log.setNomeFilaServico("TESTE3");
            logger.save(log);

            if (i == 2) {
                throw new RuntimeException();
            }
        } catch (Exception e) {
            System.out.println("RuntimeException in i = " + i);
        }
    }
}

日志服务:

package test.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import test.indicador.LogFilaServicoIndicador;
import test.model.domain.Log;
import test.repository.LogRepository;

@Service
@Transactional(transactionManager = "transactionManager", propagation = Propagation.REQUIRED)
public class LogService {

    private final LogRepository repository;

    public LogService(LogRepository repository) {
        this.repository = repository;
    }

    public Log save(Log logFilaServico) {
        return repository.save(logFilaServico);
    }
}

存储库:

package test.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import test.model.domain.Log;

@Repository
public interface LogRepository extends CrudRepository<Log, Long> {

}

【问题讨论】:

  • m2 永远不会抛出异常,因此 Spring 永远不会回滚其事务。 catch 必须在 m1 内,在 for 循环中。不在 m2() 中。
  • @JBNizet 如果我在 m2() 的 catch 块中添加“throw e”,则事务回滚(到目前为止一切顺利),但原始事务也是如此! (我的例子的交易1)。在我看来,异常只会停止内部事务,而不是 m1() 的原始。有意义吗?
  • 如果你捕捉到 m2() 抛出的异常,m1 的事务将不会被回滚。删除 m2 中的 try/catch,并将其放入 m1() 的 for 循环中(正如我在第一条评论中已经说过的)
  • 顺便说一句,m1 根本不需要是事务性的。它只是一个超过 2 个整数的循环。您不需要为此进行交易。
  • 因为 m1 是事务性的,所以对 m2 使用 REQUIRES 将简单地在为 m1 创建的唯一单个事务中完成 m2 的所有工作。 m2 抛出的任何异常都会回滚唯一的 m1 事务。如果 m1 不是事务性的(并且它的调用者都不是),则 m2 可以使用 REQUIRES,因为 REQUIRES 和 REQUIRES_NEW 都具有为 m2 启动新事务的相同效果。

标签: java spring-boot transactions


【解决方案1】:

要回滚 m2,您的异常必须跨越 service2 的 aop 包装器。

这反过来又会最终冒泡到 m1。如果您想继续 j=3,您仍然有责任编写正确的 java 代码...

像往常一样在 for 循环中捕获它。 你所有的@Transactional 都是正确的。

【讨论】:

    【解决方案2】:

    基于@JBNizet 帮助,解决我需要的问题:

    1) 从 m1() 中删除 @Transactional(transactionManager = "transactionManager", propagation = Propagation.REQUIRED)。

    2) 在 m2() 中将 REQUIRES_NEW 更改为 REQUIRED 并从 m2() 中删除 try catch 块并在 m1() 中添加一个 tryc catch 块,因为它需要在不停止 m1() 执行的情况下处理异常.

    所以我有:

    public void m1() {
    
            List<Integer> list = Arrays.asList(1, 2);
    
            for (Integer j : list) {
                try {
                    service2.m2(j);
    
                } catch (Exception e) {
                    System.out.println("Exception for j = " + j);
                }
            }
        }
    

    和 m2():

    @Transactional(transactionManager = "transactionManager", propagation = Propagation.REQUIRED)
    public void m2(int i) {
    
        Log log = new Log();
        log.setDataMensagem(new Date());
        log.setDescricaoEnvioRecebimento("TEST");
        log.setDescricaoMensagem("TEST1");
        log.setIdMensagem("TEST2");
        log.setNomeFilaServico("TESTE3");
        logger.save(log);
    
        if (i == 2) {
            throw new RuntimeException();
        }
    }
    

    【讨论】:

    • 不需要更改传播。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-14
    • 2015-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-27
    • 1970-01-01
    相关资源
    最近更新 更多