0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

发现一个Spring事务的巨坑bug 你必须要小心了

jf_ro2CN3Fa 来源:苏三说技术 作者:苏三说技术 2022-10-11 18:17 次阅读

1.错误的访问权限

2.方法被定义成final的

3.方法内部调用

4.当前实体没有被spring管理

5.错误的spring事务传播特性

6.数据库不支持事务

7.自己吞掉了异常

8.抛出的异常不正确

9.多线程调用

10.嵌套事务多回滚了

对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了。在某些业务场景下,如果同时有多张表的写入操作,为了保证操作的原子性(要么同时成功,要么同时失败)避免数据不一致的情况,我们一般都会使用spring事务。

没错,spring事务大多数情况下,可以满足我们的业务需求。但是今天我要告诉大家的是,它有很多坑,稍不注意事务就会失效。

不信,我们一起看看。

1.错误的访问权限

@Service
publicclassUserService{

@Autowired
privateUserMapperuserMapper;

@Transactional
privatevoidadd(UserModeluserModel){
userMapper.insertUser(userModel);
}
}

我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须是public的。

AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。

protectedTransactionAttributecomputeTransactionAttribute(Methodmethod,@NullableClasstargetClass){
//Don'tallowno-publicmethodsasrequired.
if(allowPublicMethodsOnly()&&!Modifier.isPublic(method.getModifiers())){
returnnull;
}

//Themethodmaybeonaninterface,butweneedattributesfromthetargetclass.
//Ifthetargetclassisnull,themethodwillbeunchanged.
MethodspecificMethod=AopUtils.getMostSpecificMethod(method,targetClass);

//Firsttryisthemethodinthetargetclass.
TransactionAttributetxAttr=findTransactionAttribute(specificMethod);
if(txAttr!=null){
returntxAttr;
}

//Secondtryisthetransactionattributeonthetargetclass.
txAttr=findTransactionAttribute(specificMethod.getDeclaringClass());
if(txAttr!=null&&ClassUtils.isUserLevelMethod(method)){
returntxAttr;
}

if(specificMethod!=method){
//Fallbackistolookattheoriginalmethod.
txAttr=findTransactionAttribute(method);
if(txAttr!=null){
returntxAttr;
}
//Lastfallbackistheclassoftheoriginalmethod.
txAttr=findTransactionAttribute(method.getDeclaringClass());
if(txAttr!=null&&ClassUtils.isUserLevelMethod(method)){
returntxAttr;
}
}

returnnull;
}

2.方法被定义成final的

@Service
publicclassUserService{

@Autowired
privateUserMapperuserMapper;

@Transactional
publicfinalvoidadd(UserModeluserModel){
userMapper.insertUser(userModel);
}
}

我们可以看到add方法被定义成了final的,这样会导致spring aop生成的代理对象不能复写该方法,而让事务失效。

3.方法内部调用

@Service
publicclassUserService{

@Autowired
privateUserMapperuserMapper;

@Transactional
publicvoidadd(UserModeluserModel){
userMapper.insertUser(userModel);
updateStatus(userModel);
}

@Transactional
publicvoidupdateStatus(UserModeluserModel){
//doSameThing();
}
}

我们看到在事务方法add中,直接调用事务方法updateStatus。从前面介绍的内容可以知道,updateStatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateStatus方法不会生成事务。

4.当前实体没有被spring管理

//@Service
publicclassUserService{

@Autowired
privateUserMapperuserMapper;

@Transactional
publicvoidadd(UserModeluserModel){
userMapper.insertUser(userModel);
}
}

我们可以看到UserService类没有定义@Service注解,即没有交给spring管理bean实例,所以它的add方法也不会生成事务。

5.错误的spring事务传播特性

@Service
publicclassUserService{

@Autowired
privateUserMapperuserMapper;

@Transactional(propagation=Propagation.NEVER)
publicvoidadd(UserModeluserModel){
userMapper.insertUser(userModel);
}

}

我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。只有这三种传播特性才会创建新事务:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。

6.数据库不支持事务

msql8以前的版本数据库引擎是支持myslam和innerdb的。我以前也用过,对应查多写少的单表操作,可能会把表的数据库引擎定义成myslam,这样可以提升查询效率。但是,要千万记得一件事情,myslam只支持表锁,并且不支持事务。所以,对这类表的写入操作事务会失效。

7.自己吞掉了异常

@Slf4j
@Service
publicclassUserService{

@Autowired
privateUserMapperuserMapper;

@Transactional
publicvoidadd(UserModeluserModel){
try{
userMapper.insertUser(userModel);
}catch(Exceptione){
log.error(e.getMessage(),e);
}
}
}

这种情况下事务不会回滚,因为开发者自己捕获了异常,又没有抛出。事务的AOP无法捕获异常,导致即使出现了异常,事务也不会回滚。

8.抛出的异常不正确

@Slf4j
@Service
publicclassUserService{

@Autowired
privateUserMapperuserMapper;

@Transactional
publicvoidadd(UserModeluserModel)throwsException{
try{
userMapper.insertUser(userModel);
}catch(Exceptione){
log.error(e.getMessage(),e);
thrownewException(e);
}
}

}

这种情况下,开发人员自己捕获了异常,又抛出了异常:Exception,事务也不会回滚。因为spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),不会回滚Exception。

9.多线程调用

@Slf4j
@Service
publicclassUserService{

@Autowired
privateUserMapperuserMapper;
@Autowired
privateRoleServiceroleService;

@Transactional
publicvoidadd(UserModeluserModel)throwsException{
userMapper.insertUser(userModel);
newThread(()->{
roleService.doOtherThing();
}).start();
}
}

@Service
publicclassRoleService{

@Transactional
publicvoiddoOtherThing(){
System.out.println("保存role表数据");
}
}

我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的,这样会导致两个事务方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。

如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。

privatestaticfinalThreadLocal>resources=
newNamedThreadLocal<>("Transactionalresources");

我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

10.嵌套事务多回滚了

publicclassUserService{

@Autowired
privateUserMapperuserMapper;

@Autowired
privateRoleServiceroleService;

@Transactional
publicvoidadd(UserModeluserModel)throwsException{
userMapper.insertUser(userModel);
roleService.doOtherThing();
}
}

@Service
publicclassRoleService{

@Transactional(propagation=Propagation.NESTED)
publicvoiddoOtherThing(){
System.out.println("保存role表数据");
}
}

这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。。但事实是,insertUser也回滚了。

why?

因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。

怎么样才能只回滚保存点呢?

@Slf4j
@Service
publicclassUserService{

@Autowired
privateUserMapperuserMapper;

@Autowired
privateRoleServiceroleService;

@Transactional
publicvoidadd(UserModeluserModel)throwsException{

userMapper.insertUser(userModel);
try{
roleService.doOtherThing();
}catch(Exceptione){
log.error(e.getMessage(),e);
}
}

}

在代码中手动把内部嵌套事务放在try/catch中,并且不继续往抛异常。

介绍到这里,你会发现spring事务的坑还是挺多的~

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2964

    浏览量

    104686
  • 数据库
    +关注

    关注

    7

    文章

    3792

    浏览量

    64340
  • spring
    +关注

    关注

    0

    文章

    340

    浏览量

    14334

原文标题:发现一个Spring事务的巨坑bug,可是官方都不承认?大家来评评理!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Spring事务失效的十种常见场景

    Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API(JPA)等事务 API,实现致的编程模
    的头像 发表于 12-11 15:03 906次阅读

    Spring事务实现原理

    作者:京东零售 范锡军 1、引言 springspring-tx模块提供事务管理支持,使用spring
    的头像 发表于 11-08 10:10 799次阅读
    <b class='flag-5'>Spring</b><b class='flag-5'>事务</b>实现原理

    什么是java spring

    ,应用对象被声明式地组合,典型地是在XML文件里。Spring也提供很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给
    发表于 09-11 11:16

    Spring事务分析的实现方式

    Spring事务原理分析
    发表于 07-02 15:19

    详解Spring事务管理

    在学习spring事务管理时,我忍不住要问,spring为什么进行事务管理,spring怎么进行的事务
    发表于 07-12 06:54

    Spring事务管理详解说明

    Spring事务管理详解
    发表于 05-20 13:46

    发现Tardis的PDA的BUG怎么解决?

    今天我怀着激动的心情把Tardis的PDA的程序(5.17最新版)烧进了板子,的确被镇住了,华丽的界面,牛叉的设计,只能用六体投地来形容只不过我点开文本浏览器时,第次是让找文本文件,然后我选
    发表于 08-19 04:23

    启动Spring Boot项目应用的三种方法

    基础。我们知道Spring Boot是什么,那么我们又该如何启动Spring Boot应用呢?这里小编给大家推荐常用的三种方法。分别是
    发表于 01-14 17:33

    苹果iOS 10.2默默修复未被发现的神级BUG知道?

     如果升级iOS 10.2,那么iPhone上那个神级Bug,终于被苹果发现并修复,虽然苹果没有提及,但还有开发者发现,苹果在iOS 10.2中修复两
    发表于 12-22 10:02 922次阅读

    spring中声明式事务实现原理猜想

      @Transactional注解简介 @Transactional 是spring中声明式事务管理的注解配置方式,相信这个注解的作用大家都很清楚。 @Transactional 注解可以帮助
    的头像 发表于 10-13 09:20 1625次阅读

    浅谈Spring事务的那些

    对于从事java开发工作的同学来说,spring事务肯定再熟悉不过了。在某些业务场景下,如果同时有多张表的写入操作,为了保证操作的原子性(要么同时成功,要么同时失败)避免数据不致的情况,我们
    的头像 发表于 10-11 10:31 741次阅读

    浅谈Spring事务底层原理

    开启Spring事务本质上就是增加了Advisor,但我们使用@EnableTransactionManagement注解来开启Spring
    的头像 发表于 12-06 09:56 688次阅读

    8Spring事务失效的场景介绍

    作为Java开发工程师,相信大家对Spring事务的使用并不陌生。但是可能只是停留在基础的使用层面上,在遇到些比较特殊的场景,事务可能
    的头像 发表于 05-11 10:41 663次阅读
    8<b class='flag-5'>个</b><b class='flag-5'>Spring</b><b class='flag-5'>事务</b>失效的场景介绍

    spring事务失效的些场景

    致的情况,我们般都会用到spring事务。 确实,spring事务用起来贼爽,就用
    的头像 发表于 10-08 14:27 441次阅读
    <b class='flag-5'>spring</b><b class='flag-5'>事务</b>失效的<b class='flag-5'>一</b>些场景

    Spring事务传播性的相关知识

    本文主要介绍Spring事务传播性的相关知识。
    的头像 发表于 01-10 09:29 434次阅读
    <b class='flag-5'>Spring</b><b class='flag-5'>事务</b>传播性的相关知识