记⼀次实际开发过程中遇到事务报错问题
Transactionsynchronizationi。。。
⼀:问题场景
在⼀次http请求的后台接⼝中返回结果中出现了这个错误信息“Transaction synchronization is not active”,意思是“事务同步器没有激活”,但是被调⽤的接⼝已经添加了@Transactional注解,所以百思不得不得其解为什么还会报这个错误,那么是什么原因引起了这个异常呢?⾃⼰通过百度⼤神博客终于到⼀篇相似的⽂章解决了⾃⼰的问题,今天就是把解决⽅案记录下来为以后再出现类似情况提供解决⽅案。
⼆:出现原因
Google搜索⼀番之后,发现了这篇⽂章Spring的TransactionEventListener,⽂中提到了可能出现这个错误信息的⼀种情况:
@EventListener
public void afterRegisterSendMail(MessageEvent event) {
// Spring 4.2 之前
springframework事务 isterSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
internalSendMailNotification(event);
}
});
}
上⾯的代码将在事务提交后执⾏.如果在⾮事务context中(就是⽅法未开启spring事务)将抛出java.lang.IllegalStateException: Transaction synchronization is not active
于是到⾃⼰代码中的TransactionSynchronizationManager部分,果然在当前http请求链路中的⼀个接⼝⾥发现了类似的代码:
isterSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
try {
byte[] bytes = JSONBytes(task, new SerializeConfig());
MessageProperties messageProperties = new MessageProperties();
messageProperties.StringId());
Message message = new Message(bytes, messageProperties);
salaryFileMsgProducer.sendSalaryFileMsg(message);
} catch (Exception e) {
(e.getMessage());
e.printStackTrace();
}
}
});
想必是执⾏这段代码的时候报的异常,但是上⾯那篇⽂章说了引起这个异常的原因是“在⾮事务context中注册同步器”,难道当前事务没有开启?我们的业务代码⼀般都是使⽤spring的注解@Transactional来开启事务,那么去看⼀下开启事务的代码⽚段。
三:根本原因
我在调⽤isterSynchronization()的⽅法体上到了@Transactional注解,⽅法代码如下,省略具体实现:
@Transactional(rollbackFor = Exception.class)
public Boolean creatSalaryMonthStatistic(SalaryMonthlyStatistic salaryMonthlyStatistic){
....
}
这就奇怪了,明明⽅法上已经添加@Transactional注解开启事务,为什么事务开启失败,带着这个疑问再次求助了Google,发现了这篇博客spring的service类调⽤⾃⼰⽅法事务⽆效,作者也遇到了事务⽆效的问题,其中总结了事务有效性相关的⼏点重要信息:
1.在需要事务管理的地⽅加@Transactional 注解。@Transactional 注解可以被应⽤于接⼝定义和接⼝⽅法、类定义和类的 public ⽅法上。
2.@Transactional 注解只能应⽤到 public 可见度的⽅法上。如果你在 protected、private 或者 package-visible 的⽅法上使⽤
@Transactional 注解,它也不会报错,但是这个被注解的⽅法将不会展⽰已配置的事务设置。
3.注意仅仅 @Transactional 注解的出现不⾜于开启事务⾏为,它仅仅是⼀种元数据。必须在配置⽂件中使⽤配置元素,才真正开启了事务⾏为。(spring配置⽂件中,开启声明式事务)
4.通过元素的 “proxy-target-class” 属性值来控制是基于接⼝的还是基于类的代理被创建。如果 “proxy-t
arget-class” 属值被设置为“true”,那么基于类的代理将起作⽤(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 “proxy-target-class” 属值被设置为 “false” 或者这个属性被省略,那么标准的JDK基于接⼝的代理将起作⽤。
5.Spring团队建议在具体的类(或类的⽅法)上使⽤ @Transactional 注解,⽽不要使⽤在类所要实现的任何接⼝上。在接⼝上使⽤
@Transactional 注解,只能当你设置了基于接⼝的代理时它才⽣效。因为注解是不能继承的,这就意味着如果正在使⽤基于类的代理时,那么事务的设置将不能被基于类的代理所识别,⽽且对象也将不会被事务代理所包装。
6.@Transactional的事务开启,或者是基于接⼝的或者是基于类的代理被创建。所以在同⼀个类中⼀个⽆事务的⽅法调⽤另⼀个有事务的⽅法,事务是不会起作⽤的。
特别注意第6点:同⼀个类中⼀个⽆事务的⽅法调⽤另⼀个有事务的⽅法,事务是不会起作⽤的。这⼀点引起了我的注意,可能我的加了注解@Transactional的creatSalaryMonthStatistic⽅法也是被类⾥另⼀个没有开启事务的⽅法调⽤,如果真的是这样,所有⼀切都解释的通了。所以带着这个思路检查了⼀遍⾃⼰的代码,
果然,creatSalaryMonthStatistic⽅法被⼀个没加事务的接⼝⽅法调⽤,从⽽整个creatSalaryMonthStatistic⽅法都没有事务效果:
//此接⼝⽅法未加事务
public Boolean batchInitSalaryFileDataTask4Tenant(String tenantId){
.....
ateSalaryFileDateInitTask4Staff(file);
.....
}
四:解决⽅案
知道了引起问题的原因,解决⽅法也⽐较简单,有好⼏种⽅法都可以解决这个问题,可以根据个⼈需要选择⼀种解决⽅法,本⽂列出其中三种:
1.⽐较⽅便、暴⼒的⼀种⽅法就是直接在最外层接⼝⽅法中添加事务注解,也就是给本⽂中的batchInit
SalaryFileDataTask4Tenant⽅法加上注解即可。
2.也可以通过代理类调⽤creatSalaryMonthStatistic⽅法,代理类中包含了事务逻辑,这样也能实现事务功能。
3.还可以直接将creatSalaryMonthStatistic这个⽅法放到另外⼀个类中。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论