事务传播机制之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仍被主事务回滚问题

浙公网安备 33010602011771号