@Transactionalspring事务⽆效的解决⽅案
关于@Transactional注解⼀般都认为要注意以下三点
1 .在需要事务管理的地⽅加@Transactional 注解。@Transactional 注解可以被应⽤于接⼝定义和接⼝⽅法、类定义和类的 public ⽅法上。
2 . @Transactional 注解只能应⽤到 public 可见度的⽅法上。如果你在 protected、private 或者 package-visible 的⽅法上使⽤ @Transactional 注解,它也不会报错,但是这个
被注解的⽅法将不会展⽰已配置的事务设置。
3 . 注意仅仅 @Transactional 注解的出现不⾜于开启事务⾏为,它仅仅是⼀种元数据。必须在配置⽂件中使⽤配置元素,才真正开启了事务⾏为。
最近在项⽬中发现注解⽆效,经过跟踪源代码发现了问题,于是在⽹上到相同出现此问题的⼈,以下为原⽂,讲解的很详细:
只要避开Spring⽬前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在⽅法⾥使⽤编程式事务
[问题]
Spring的声明式事务,我想就不⽤多介绍了吧,⼀句话“⾃从⽤了Spring AOP啊,事务管理真轻松啊,真轻松;事务管理代码没有了,脑不酸了,⼿不痛了,⼀⼝⽓全配上了事
务;轻量级,测试起来也简单,嘿!”。不管从哪个⾓度看,轻量级声明式事务都是⼀件解放⽣产⼒的⼤好事。所以,我们“⼀直⽤它”。
不过,最近的⼀个项⽬⾥,却碰到了⼀个事务管理上的问题:有⼀个服务类,其⼀个声明了事务的⽅法,⾥⾯做了三次插⼊SQL操作,但是在后⾯出错回滚时,却发现前⾯插⼊成功了,也是说,这个声明了事务的⽅法,实际上并没有真正启动事务![探幽]
其实以前也会碰到有⼈说,Spring的事务配置不起作⽤,但是根据第⼀反应和以往经验,我总会告诉他,肯定是你的配置有问题啦;所以这⼀次,我想也不会例外,⼤概是把事
务注解配在了接⼝上⽽不是实现⽅法上,或者,如果是⽤XML声明⽅式的话,很可能是切⼊点的表达式没有配对。
不过,在检查了他们的配置后,却发现没有配置问题,该起事务的实现⽅法上,⽤了@Transactional事务注解声明,XML⾥也配了注解驱动<tx:annotation-driven .../>,配置很正确啊,怎么会不起作⽤?
我很纳闷,于是往下问:
问1:其他⽅法有这种情况么?
答1:没有。
问2:这个⽅法有什么特别的么(以下简称⽅法B)?
答2:就是调后台插了三条记录啊,没啥特别的。
问3:这个⽅法是从Web层直接调⽤的吧?
答3:不是,是这个Service类(以下简称ServiceA)的另外⼀个⽅法调过来的(以下简称⽅法A)。
问4:哦,那个调⽤它的⽅法配了事务么(问题可能在这了)?
答4:没有。
问5:那WEB层的Action(⽤的是Struts2),调⽤的是没有声明事务的⽅法A,⽅法A再调⽤声明了事务的⽅法B?
答5:对的。
问6:你直接在⽅法A上加上事务声明看看
答6:好。。。
看来可能到问题所在了,于是把@Transactional也加在⽅法A上,启动项⽬测试,结果是:事务正常⽣效,⽅法A和⽅法B都在⼀个事务⾥了。
好了,现在总结⼀下现象:
1、ServiceA类为Web层的Action服务
2、Action调⽤了ServiceA的⽅法A,⽽⽅法A没有声明事务(原因是⽅法A本⾝⽐较耗时⽽⼜不需要事务)
3、ServiceA的⽅法A调⽤了⾃⼰所在class的⽅法B,⽽⽅法B声明了事务,但是⽅法B的事务声明在这种情况失效了。
4、如果在⽅法A上也声明事务,则在Action调⽤⽅法A时,事务⽣效,⽽⽅法B则⾃动参与了这个事务。
我让他先把A也加上事务声明,决定回来⾃⼰再测⼀下。
这个问题,表⾯上是事务声明失效的问题,实质上很可能是Spring的AOP机制实现⾓度的问题。
我想到很久以前研究Spring的AOP实现时发现的⼀个现象:对于以Cglib⽅式增强的AOP⽬标类,会创建两个对象,⼀个事Bean实例本⾝,⼀个是Cglib增强代理对象,⽽不仅仅
是只有后者。我曾经疑惑过这⼀点,但当时没有再仔细探究下去。
我们知道,Spring的AOP实现⽅式有两种:1、Java代理⽅式;2、Cglib动态增强⽅式,这两种⽅式在Spring中是可以⽆缝⾃由切换的。
Java代理⽅式的优点是不依赖第三⽅jar包,缺点是不能代理类,只能代理接⼝。
Spring通过AopProxy接⼝,抽象了这两种实现,实现了⼀致的AOP⽅式:
现在看来,这种抽象同样带了⼀个缺陷,那就是抹杀了Cglib能够直接创建普通类的增强⼦类的能⼒,Spring相当于把Cglib动态⽣成的⼦类,当普通的代理类了,这也是为什么
会创建两个对象的原因。下图显⽰了Spring的AOP代理类的实际调⽤过程:
因此,从上⾯的分析可以看出,methodB没有被AopProxy通知到,
导致最终结果是:
被Spring的AOP增强的类,在同⼀个类的内部⽅法调⽤时,其被调⽤⽅法上的增强通知将不起作⽤。
⽽这种结果,会造成什么影响呢:
1:内部调⽤时,被调⽤⽅法的事务声明将不起作⽤
2:换句话说,你在某个⽅法上声明它需要事务的时候,如果这个类还有其他开发者,你将不能保证这个⽅法真的会在事务环境中
3:再换句话说,Spring的事务传播策略在内部⽅法调⽤时将不起作⽤。
不管你希望某个⽅法需要单独事务,是RequiresNew,还是要嵌套事务,要Nested,等等,统统不起作⽤。
4:不仅仅是事务通知,所有你⾃⼰利⽤Spring实现的AOP通知,都会受到同样限制。。。。
[解难]
问题的原因已经到,其实,我理想中的AOP实现,应该是下⾯这样:
只要⼀个Cglib增强对象就好,对于Java代理⽅式,我的选择是毫不犹豫的抛弃。
⾄于前⾯的事务问题,只要避开Spring⽬前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在⽅法⾥使⽤编程式事务,那么⼀切OK。
本⽂转⾃:
spring+springMVC,声明式事务失效,原因以及解决办法
⼀.声明式事务配置:
<context:component-scan base-package="com.ada.wuliu"/>
<context:component-scan base-package="com.wechat"/>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="del*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED"
rollback-for="Exception"/>
<tx:method name="updateOrderPayBackNotfiy" propagation="REQUIRED"
isolation="SERIALIZABLE"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
⼆.声明式事务失效,原因
根本原因:由⼦容器扫描装配了@Service 注解的实例。
spring的context是⽗⼦容器,由ServletContextListener 加载spring配置⽂件产⽣的是⽗容器,springMVC加载配置⽂件产⽣的是⼦容器,⼦容器对Controller进⾏扫描装配时装配
了@Service注解的实例 (@Controller 实例依赖@Service实例),⽽该实例理应由⽗容器进⾏初始化以保证事务的增强处理,所以此时得到的将是原样的Service没有经过事务加强处理,故⽽没有事务处理能⼒。
三.解决办法
1.修改spring配置⽂件:
<!-- 不扫描带有@Controller注解的类 ,让 springMVC ⼦容器加载。 -->
<context:component-scan base-package="com.ada.wuliu">spring aop应用场景
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
其他事务不起作⽤:
@Transactional 注解属性 propagation 设置错误
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发⽣回滚。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。 TransactionDefinition.PROPAGATION_NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承⾃ RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。
// 希望⾃定义的异常可以进⾏回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class
若在⽬标⽅法中抛出的异常是 rollbackFor 指定的异常的⼦类,事务同样会回滚。Spring源码如下:
private int getDepth(Class<?> exceptionClass, int depth) {
if (Name().ptionName)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't
if (exceptionClass == Throwable.class) {
return -1;
}
return Superclass(), depth + 1);
}
同⼀个类中⽅法调⽤,导致@Transactional失效
开发中避免不了会对同⼀个类⾥⾯的⽅法调⽤,⽐如有⼀个类Test,它的⼀个⽅法A,A再调⽤本类的⽅法B(不论⽅法B是⽤public还是private修饰),但⽅法A没有声明注解事务,⽽B⽅法有。则外部调⽤⽅法A之后,⽅法B的事务是不会起作⽤的。这也是经常犯错误的⼀个地⽅。
那为啥会出现这种情况?其实这还是由于使⽤Spring AOP代理造成的,因为只有当事务⽅法被当前类以外的代码调⽤时,才会由Spring⽣成的代理对象来管理。
//@Transactional
@GetMapping("/test")
private Integer A() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
/**
* B 插⼊字段为 3的数据
*/
this.insertB();
/**
* A 插⼊字段为 2的数据
*/
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert;
}
@Transactional()
public Integer insertB() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("3");
cityInfoDict.setParentCityId(3);
return cityInfoDictMapper.insert(cityInfoDict);
}
异常被你的 catch“吃了”导致@Transactional失效
这种情况是最常见的⼀种@Transactional注解失效场景,
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 插⼊字段为 2的数据
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插⼊字段为 3的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果B⽅法内部抛了异常,⽽A⽅法此时try catch了B⽅法的异常,那这个事务还能正常回滚吗?
答案:不能!
会抛出异常:
ansaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
因为当ServiceB中抛出了⼀个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你⼿动的捕获这个异常并进⾏处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不⼀致,也就是因为这样,抛出了前⾯的UnexpectedRollbackException异常。
spring的事务是在调⽤业务⽅法之前开始的,业务⽅法执⾏完毕之后才执⾏commit or rollback,事务是否执⾏取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务⽅法中没有catch到的话,事务会回滚。
在业务⽅法中⼀般不需要catch异常,如果⾮要catch⼀定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不⼀致,所以有些时候try catch反倒会画蛇添⾜。
数据库引擎不⽀持事务
这种情况出现的概率并不⾼,事务能否⽣效数据库引擎是否⽀持事务是关键。常⽤的MySQL数据库默认使⽤⽀持事务的innodb引擎。⼀旦数据库引擎切换成不⽀持事务的myisam,那事务就从根本上失效了。
启⽤新线程调⽤本类中的⽅法,这个新线程不会加⼊事务管理
没有事物的⽅法调⽤本类有事物⽅法也⽆效,⼀样的,需要改成⾛代理类即可
Dubbo的注解和spring的transtion注解⼀起⽤会使dubbo的注解⽆效

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。