事务传播机制之REQUIRES_NEW

起因是完成本学期JavaEE课程作业之JdbcTemplate使用与Transactional中几种事务传播机制,其中有一个场景就是主事务中调用另一个事务,然后在主事务中引发异常,要求不影响到调用的事务。显然,应该使用REQUIRES_NEW方式传播(即被调用的事务方法使用@Transactional(propagation = Propagation.REQUIRES_NEW)来开启一个新事务

样例代码如下:

	...
	@Override
    @Transactional
    public void RollBackAfterLog(String fromAccount, String toAccount, BigDecimal amount) throws Exception {
        transfer(fromAccount, toAccount, amount);
//        System.out.println("outside obj: " + beanFactory.getBean(BankService.class).getClass());
        Integer transactionId = logTransaction(fromAccount, toAccount, amount);
        if (amount.intValue() > 5) throw new RuntimeException("转账金额过大");
        auditTransaction(transactionId, "FENDING");
    }

	@Transactional(propagation = Propagation.REQUIRES_NEW) // 创建新的事务 不受影响
    @Override
    public Integer logTransaction(String fromAccount, String toAccount, BigDecimal amount) {
        return transactionLogDao.insert(fromAccount, toAccount, amount);
    }
	...

但是遗憾的是,以上代码会导致主事务和被调用事务都会滚!

(此段为废话)我一脸蒙蔽,这不是骗人吗,说好的开启新事务呢!接着我逐步调试,发现数据库确实从未新增过数据,蛤?其实这里我调试时会先进入代理类的方法体,我就该有所联想...但是受于智商所限,我并没有发现问题所在,遂询问chat-gpt,gpt乱说一通,不曾切入要害,最终只能搜索博客求解,还好普天之下高手云集+我的关键词搜索能力出群(bushi)终于找到的解决的方法。

首先,@Transactional的实现原理,其实就是AOP代理,在这个代理方法内自动的加上事务的开启以及事务的提交或者回滚。所以问题也就出在这里,在主事务方法的地方,确实是代理类来调用的,但是,主事务调用别的事务方法却是调用的原生的方法,类似于如下情况:

// 代理对象 BankServiceImplProxy
public class BankServiceImplProxy extends BankServiceImpl {
    @Override
    public void RollBackAfterLog(...) {
        // 事务管理代码
        super.RollBackAfterLog(...);
        // 提交或回滚事务
    }

    @Override
    public void logTransaction(...) {
        // 事务管理代码
        super.logTransaction(...);
        // 提交或回滚事务
    }
}

所以这里的logTransaction()的事务被绕过了,从而导致了上述的错误。

最后,解决方法就是:手动调用代理类的代理方法而非原生的非事务方法。具体方式有两种,一个是通过AopContext获取对应的代理类,另一种就是通过BeanFactory获取代理类(getBean() or DI)。我这里采取的第二种,但是需要注意循环依赖的问题。

	private BankService bankServiceProxy;

    @Resource
    public void setBankServiceProxy(@Lazy BankService bankService) { // 延迟初始化 避免循环依赖
        this.bankServiceProxy = bankService;
    }


	@Override
    @Transactional
    public void RollBackAfterLog(String fromAccount, String toAccount, BigDecimal amount) throws Exception {
        bankServiceProxy.transfer(fromAccount, toAccount, amount);
//        System.out.println("outside obj: " + beanFactory.getBean(BankService.class).getClass());
        Integer transactionId = bankServiceProxy.logTransaction(fromAccount, toAccount, amount);
        if (amount.intValue() > 5) throw new RuntimeException("转账金额过大");
        bankServiceProxy.auditTransaction(transactionId, "FENDING");
    }

参考文章:

解决Spring子事务新开事务REQUIRES_NEW仍被主事务回滚问题

spring事务传播行为之使用REQUIRES_NEW不回滚

spring事务传播机制

posted @ 2024-11-10 00:29  yuqiu2004  阅读(121)  评论(0)    收藏  举报