当前位置: 首页 > 后端技术 > Java

百度这边:说说@Transactional的原理和坑

时间:2023-04-01 16:04:04 Java

JavaBackend面试,面试官经常问@Transactional的原理和容易踩的坑。以前在百度上遇到过,今天就带大家把这几块知识彻底的了解一遍。在这篇文章中,我们将首先描述@Transactional的4个无效的Cases,然后通过源码解读分析@Transactional的执行原理以及某些Cases不生效的真正原因。项目准备下面是DB数据和DB操作接口:uidunameusex1张三女2陈恒南3楼再南//提供接口publicinterfaceUserDao{//select*fromuser_testwhereuid="#{uid}"publicMyUserselectUserById(Integeruid);//updateuser_testsetuname=#{uname},usex=#{usex}whereuid=#{uid}publicintupdateUser(MyUseruser);}复制代码基本测试代码,testSuccess()为交易生效情况:@ServicepublicclassUserController{@AutowiredprivateUserDaouserDao;publicvoidupdate(Integerid){MyUseruser=newMyUser();user.setUid(id);useruser.setUname("张三-测试");.setUsex("女");userDao.updateUser(用户);}publicMyUserquery(Integerid){MyUseruser=userDao.selectUserById(id);返回用户;)publicvoidtestSuccess()throwsException{Integerid=1;我的用户用户=查询(id);System.out.println("原始记录:"+user);更新(编号);抛出新的Exception("交易生效");}}复制代码事务不生效的几种情况主要说明事务不生效的4种情况:类内部访问:A类的a1方法没有标注@Transactional,而a2方法标注了使用@Transactional,在a1中调用a2;私有方法:在非公共方法上标注@Transactional注解;异常不匹配:@Transactional没有设置rollbackFor属性,方法返回Exception等异常;多线程:主线程和子线程的调用,线程抛异常案例1:类内部访问我们在类UserController中添加一个新方法testInteralCall():publicvoidtestInteralCall()throwsException{testSuccess();thrownewException("事务不生效:类内部访问");}把代码复制到这里testInteralCall()没有标上@Transactional,我们再来看测试用例:publicstaticvoidmain(String[]args)抛出异常{ApplicationContextapplicationContext=newClassPathXmlApplicationContext("applicationContext.xml");UserControlleruc=(UserController)applicationContext.getBean("userController");尝试{uc.testSuccess();}最后{MyUseruser=uc.query(1);System.out.println("修改记录:"+user);}}//输出://原始记录:MyUser(uid=1,uname=ZhangSan,usex=female)//修改记录:MyUser(uid=1,uname=ZhangSan-testing,usex=female)复制从上面输出的代码可以看出事务没有回滚。这是什么原因?因为@Transactional的工作机制是基于AOP的,而AOP是使用动态代理实现的。如果直接通过代理调用testSuccess(),会在AOP前后进行增强。增强的逻辑其实就是在testSuccess()前后添加open,提交事务的逻辑后面会在源码中分析。现在testSuccess()是通过testInteralCall()调用的,testSuccess()前后不会做任何增强,即类内部调用,不会通过代理访问。如果你还不清楚,我建议再读一遍这篇文章。里面有完整的例子,完美的解释了为什么“类内部访问”不能前后增强的原因:blog.csdn.net/Ahuuua/arti...案例二:私有方法在private方法上,添加@Transactional注解不会生效:@Transactional(rollbackFor=Exception.class)privatevoidtestPirvateMethod()throwsException{Integerid=1;我的用户用户=查询(id);System.out.println("原始记录:"+user);更新(编号);thrownewException("Testtransactiontakeseffect");}复制代码直接使用时,不容易出现下面的场景,因为IDEA会提醒你,复制的是:Methodsannotatedwith'@Transactional'mustbeoverridable,至于深入原理,源码部分给大家讲解。情况3:Exception不匹配@Transactional这里没有设置rollbackFor=Exception.class属性:@TransactionalpublicvoidtestExceptionNotMatch()throwsException{Integerid=1;我的用户用户=查询(id);System.out.println("原始记录:"+user);更新(编号);thrownewException("交易不生效:异常不匹配");}复制代码测试方法:同Case1//输出://原始记录:User[uid=1,uname=张三,usex=female]//修改记录:User[uid=1,uname=张三-test,usex=female]复制代码@Transactional注解默认处理运行时异常,即只抛出运行时异常,才会触发事务回滚,否则不会回滚。至于深入的原理,源码部分会给大家讲解。Case4:多线程下面给出两种不同的姿势。一种是子线程抛出异常,主线程没问题;另一种是子线程没问题,主线程抛出异常。父线程抛出异常父线程抛出异常,子线程不抛出异常:publicvoidtestSuccess()throwsException{Integerid=1;我的用户用户=查询(id);System.out.println("原始记录:"+user);update(id);}@Transactional(rollbackFor=Exception.class)publicvoidtestMultThread()抛出Exception{newThread(newRunnable(){@SneakyThrows@Overridepublicvoidrun(){testSuccess();}).开始();thrownewException("测试事务不生效");}复制代码父线程抛出线程,事务回滚,因为子线程独立存在,与父线程不在同一个事务中,所以子线程对线程的修改不会回滚,子线程抛出异常。父线程没有抛出异常,子线程抛出异常:publicvoidtestSuccess()throwsException{Integerid=1;我的用户用户=查询(id);System.out.println("原始记录:"+user);更新(编号);thrownewException("测试事务不生效");}@Transactional(rollbackFor=Exception.class)publicvoidtestMultThread()throwsException{newThread(newRunnable(){@SneakyThrows@Overridepublicvoidrun(){testSuccess();}}).start();}由于子线程异常线程捕获,所以父线程没有抛出异常,事务回滚不生效源码解读下面我们从源码的角度解读一下@Transactional的执行机制以及事务不生效的原因。我们只看@Transactional执行机制的核心逻辑。代码中的interceptorOrInterceptionAdvice是TransactionInterceptor的一个实例,入参就是这个对象。红框内有注释,大致翻译为“它是一个拦截器,所以我们只需要调用它:在构造这个对象之前,会静态计算入口点”。这是ReflectiveMethodInvocation对象,成员对象包含UserController类、testSuccess()方法、入参和代理对象等。进入invoke()方法后:高能前行!!!这里是事务的核心逻辑,包括判断事务是否开启、执行目标方法、回滚事务、提交事务。上图中第一个红框区域调用方法getTransactionAttribute(),主要是获取txAttr变量,用于读取@Transactional配置。如果这个txAttr=null,后面就没有交易逻辑了,我们看一下这个变量的含义:我们直接进入getTransactionAttribute(),重点关注获取交易配置的方法。前方高能!!!这就是private导致交易不生效的原因。allowPublicMethodsOnly()始终返回false,因此重点仅放在isPublic()方法上。接下来通过位和计算判断是否为Public。对应的修饰符类型如下:PUBLIC:1PRIVATE:2PROTECTED:4看到这里,是不是豁然开朗了?是不是觉得很有意思~~异常不匹配的原因,我们继续回到事务的核心逻辑,因为main方法抛出Exception()异常,进入事务回滚的逻辑:进入rollbackOn()方法判断异常是否可以回滚,这个需要判断main方法抛出的Exception()异常是否在@Transactional的配置中:我们进入getDepth()查看异常规则匹配逻辑,因为我们已经为@Transactional配置了rollbackFor=Exception.class,所以可以匹配成功:例子中的winner不为null,所以会跳过下面的链接。但是当winner=null时,即没有设置rollbackFor属性时,会使用默认的异常捕获方式。前方高能!!!这是异常不匹配的原因。我们来看看默认的异常捕获方式:是不是突然明白了,在没有设置rollbackFor属性的情况下,默认只对RuntimeException和Error异常进行回滚。作者:楼仔链接:https://juejin.cn/post/713226...来源:稀土掘金版权归作者所有。商业转载请联系作者授权,非商业转载请注明出处。