必须在事务上下文之外调用方法 - Spring @Transactional

Method must be called outside of transactional context - Spring @Transactional

希望你一切都好。

我想找到确保在事务外调用服务方法的最佳方法。它将如下所示:

假设我们有一个以下形式的方法:

@Transactional
public void insertEntity(Entity entity){
    persistence.save(entity);
}

现在,假设我们正在调用此方法,但我们需要确保它不会在已经是事务性代码的内部调用。以下是错误的:

@Transactional
public void enclosingTransaction() {
    //Perform long process transaction
    service.insertEntity(entity);
}

让我们的方法 "insertEntity" 知道正在 运行 事务中调用并抛出错误的最佳选择是什么?

谢谢!

您可以调用 TransactionAspectSupport.currentTransactionStatus().isNewTransaction() 方法来了解当前交易是否是新的(即它不是从另一个 @Transactional 方法传播的):

@Transactional
public void insertEntity(Entity entity){
    if (!TransactionAspectSupport.currentTransactionStatus().isNewTransaction()) {
        throw new IllegalStateException("Transaction is not new!");
    }
    persistence.save(entity);
}

静态方法TransactionAspectSupport.currentTransactionStatus() returns一个TransactionStatus对象表示当前方法调用的事务状态.


我写了一个最小的 Spring MVC webapp 来测试你的场景(我省略了配置 类 和文件,以及 importpackages 声明):

TestController.java

@RestController
public class TestController {

    private static final Logger log = LoggerFactory.getLogger(TestController.class);

    @Autowired
    private ServiceOne serviceOne;

    @Autowired
    private ServiceTwo serviceTwo;

    @GetMapping(path = "/test-transactions")
    public String testTransactions() {
        log.info("*** TestController.testTransactions() ***");
        log.info("* Invoking serviceOne.methodOne()...");
        try {
            serviceOne.methodOne();
        }
        catch (IllegalStateException e) {
            log.error("* {} invoking serviceOne.methodOne()!", e.getClass().getSimpleName());
        }
        log.info("* Invoking serviceTwo.methodTwo()...");
        try {
            serviceTwo.methodTwo();
        }
        catch (IllegalStateException e) {
            log.error("* {} invoking serviceTwo.methodTwo()!", e.getClass().getSimpleName());
        }
        return "OK";
    }
}

ServiceOneImpl.java

@Service
public class ServiceOneImpl implements ServiceOne {

    private static final Logger log = LoggerFactory.getLogger(ServiceOneImpl.class);

    @Autowired
    private ServiceTwo serviceTwo;

    @PersistenceContext
    private EntityManager em;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodOne() {
        log.info("*** ServiceOne.methodOne() ***");
        log.info("getCurrentTransactionName={}", TransactionSynchronizationManager.getCurrentTransactionName());
        log.info("isNewTransaction={}", TransactionAspectSupport.currentTransactionStatus().isNewTransaction());
        log.info("Query result={}", em.createNativeQuery("SELECT 1").getResultList());
        log.info("getCurrentTransactionName={}", TransactionSynchronizationManager.getCurrentTransactionName());
        log.info("isNewTransaction={}", TransactionAspectSupport.currentTransactionStatus().isNewTransaction());
        serviceTwo.methodTwo();
    }
}

ServiceTwoImpl.java

@Service
public class ServiceTwoImpl implements ServiceTwo {

    private static final Logger log = LoggerFactory.getLogger(ServiceTwoImpl.class);

    @PersistenceContext
    private EntityManager em;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodTwo() {
        log.info("*** ServiceTwo.methodTwo() ***");
        log.info("getCurrentTransactionName={}", TransactionSynchronizationManager.getCurrentTransactionName());
        log.info("isNewTransaction={}", TransactionAspectSupport.currentTransactionStatus().isNewTransaction());
        if (!TransactionAspectSupport.currentTransactionStatus().isNewTransaction()) {
            log.warn("Throwing exception because transaction is not new...");
            throw new IllegalStateException("Transaction is not new!");
        }
        log.info("Query result={}", em.createNativeQuery("SELECT 2").getResultList());
        log.info("getCurrentTransactionName={}", TransactionSynchronizationManager.getCurrentTransactionName());
        log.info("isNewTransaction={}", TransactionAspectSupport.currentTransactionStatus().isNewTransaction());
    }
}

这是执行的 log

INFO test.transactions.web.TestController - *** TestController.testTransactions() ***
INFO test.transactions.web.TestController - * Invoking serviceOne.methodOne()...
INFO test.transactions.service.ServiceOneImpl - *** ServiceOne.methodOne() ***
INFO test.transactions.service.ServiceOneImpl - getCurrentTransactionName=test.transactions.service.ServiceOneImpl.methodOne
INFO test.transactions.service.ServiceOneImpl - isNewTransaction=true
INFO test.transactions.service.ServiceOneImpl - Query result=[1]
INFO test.transactions.service.ServiceOneImpl - getCurrentTransactionName=test.transactions.service.ServiceOneImpl.methodOne
INFO test.transactions.service.ServiceOneImpl - isNewTransaction=true
INFO test.transactions.service.ServiceTwoImpl - *** ServiceTwo.methodTwo() ***
INFO test.transactions.service.ServiceTwoImpl - getCurrentTransactionName=test.transactions.service.ServiceOneImpl.methodOne
INFO test.transactions.service.ServiceTwoImpl - isNewTransaction=false
WARN test.transactions.service.ServiceTwoImpl - Throwing exception because transaction is not new...
ERROR test.transactions.web.TestController - * IllegalStateException invoking serviceOne.methodOne()!
INFO test.transactions.web.TestController - * Invoking serviceTwo.methodTwo()...
INFO test.transactions.service.ServiceTwoImpl - *** ServiceTwo.methodTwo() ***
INFO test.transactions.service.ServiceTwoImpl - getCurrentTransactionName=test.transactions.service.ServiceTwoImpl.methodTwo
INFO test.transactions.service.ServiceTwoImpl - isNewTransaction=true
INFO test.transactions.service.ServiceTwoImpl - Query result=[2]
INFO test.transactions.service.ServiceTwoImpl - getCurrentTransactionName=test.transactions.service.ServiceTwoImpl.methodTwo
INFO test.transactions.service.ServiceTwoImpl - isNewTransaction=true