记一次事务报错问题 Transaction synchronization is not active
问题场景
在一次请求的返回结果中出现了这个错误信息“Transaction synchronization is not active”,意思是“事务同步器没有激活”,看着不像是业务代码里返回的提示,猜测是spring事务框架报出来的异常没有被捕获而报的错,在代码里一通搜索之后确实没发现这个提示信息,那是什么原因引起了这个异常呢?Google之。
出现原因
Google一番之后,发现了这篇文章Spring的TransactionEventListener,文中提到了可能出现这个错误信息的一种情况:
@EventListener
public void afterRegisterSendMail(MessageEvent event) {
// Spring 4.2 之前
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
internalSendMailNotification(event);
}
});
}
上面的代码将在事务提交后执行.如果在非事务context中将抛出java.lang.IllegalStateException: Transaction synchronization is not active
于是去代码中搜索TransactionSynchronizationManager,果然在当前请求链路中的一个service里发现了类似的代码:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
logger.info("afterCommit_car_move_to_otherwarehouse_carIds:{}", new Gson().toJson(form.getCarIdList()));
for (Long wmsCarId : form.getCarIdList()) {
MovedCarMessage movedCarMessage = new MovedCarMessage();
movedCarMessage.setOldCarId(wmsCarId);
Message message = MessageBuilder.of(movedCarMessage).topic(carMoveToOtherWarehouseMessageTopic).build();
messageProducer.syncSend(message);
}
}
});
想必是执行这段代码的时候报的异常,但是上面那篇文章说了引起这个异常的原因是“在非事务context中注册同步器”,难道当前事务没有开启?我们的业务代码一般都是使用spring的注解@Transactional来开启事务,那么去看一下开启事务的代码片段。
根本原因
我在调用TransactionSynchronizationManager.registerSynchronization()
的方法体上找到了@Transactional注解,方法代码如下,省略具体实现:
@Transactional
@Override
public BizResult departingCar(CarDepartingForm form, User user) {
···
}
这就奇怪了,明明方法上已经添加@Transactional注解开启事务,为什么事务开启失败,带着这个疑问再次求助了Google,发现了这篇博客spring的service类调用自己方法事务无效,作者也遇到了事务无效的问题,其中总结了事务有效性相关的几点重要信息:
1.在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。
2.@Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
3.注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。(spring配置文件中,开启声明式事务)
4.通过 元素的 “proxy-target-class” 属性值来控制是基于接口的还是基于类的代理被创建。如果 “proxy-target-class” 属值被设置为 “true”,那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 “proxy-target-class” 属值被设置为 “false” 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。
5.Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。
6.@Transactional的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的。
特别注意第6点:同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的。这一点引起了我的注意,可能我的加了注解@Transactional的departingCar方法也是被类里另一个没有开启事务的方法调用,如果真的是这样,所有一切都解释的通了。
果然,departingCar方法被一个没加事务的接口方法调用,从而整个方法都没有事务效果:
//此接口方法未加事务
@Override
public BizResult departingCarFlowFinish(CarDepartingForm form, User user) {
···
//在此调用事务方法
BizResult departResult = departingCar(form, user);
···
}
解决方案
知道了引起问题的原因,解决方法也比较简单,有好几种方法都可以解决这个问题,本文列出其中两种:
1.比较方便、暴力的一种方法就是直接在最外层接口方法中添加事务注解,也就是给本文中的departingCarFlowFinish方法加上注解即可。
2.也可以通过代理类调用departingCar方法,代理类中包含了事务逻辑,这样也能实现事务功能。