Springboot开启了事务的方法调用没有事务的方法:
提示:上方标题是一个很笼统的场景,详情展开如下,先说结论:
总结:
方法A调用方法B:
场景一 如果A和B方法在同一个类中:
如果A加@Transactional注解,B加不加@Transactional注解,事务是有效的,则AB在同一事务中。
如果A不加@Transactional注解,B加不加@Transactional注解,事务都是无效的。(
但是如果使用代理类来进行b方法调用,那么B会开启事务,A不会开启事务
)
场景二 如果A和B不在同一个类中:
如果A加@Transactional注解,B加不加@Transactional注解,事务是有效的。
如果A不加@Transactional注解,B加了@Transactional注解,只有B是有事务的;
如果A不加@Transactional注解,B不加@Transactional注解,A、B都是没有事务的。
或者这样理解
1、如果A加@Transactional注解,不管是不是在一个类中,不管B加不加注解,AB都是在同一事务中;
2、如果A不加@Transactional注解,只有B加@Transactional注解,AB方法为同一类,事务失效(使用代理类,B方法有事务);AB不同类,只有B有事务;
3、如果A不加@Transactional注解,B不加@Transactional注解,则没有事务;
原因:A方法上有@Transactional注解,spring在管理的时候,会生成一个代理类,真正调用到A方法时,实际执行的是代理类里面的方法,该代理类里面的方法已经包括了B方法的调用,已经成为了一个方法。所以事务是有效的。
**注意:代码中TestUserServiceImpl的
update方法
和
updateOther方法
,在全文中简称为A方法**
场景一 A和B方法在同一个类中:
我们先自己建立一个controller,service,以及mapper。代码如下
@RestController@RequestMapping("/testUser")publicclassTestShiWuController{@AutowiredprivateITestService testService;@GetMapping("/update")publicvoidupdate(String param){
testService.update(param);}}
@ServicepublicclassTestUserServiceImplimplementsITestService{@AutowiredprivateTestUserMapper testUserMapper;@Override@Transactional(rollbackFor =Exception.class)publicintupdate(String param){
testUserMapper.updateUserById("a",1L);b(param);c(param);return0;}// @Transactional(rollbackFor =Exception.class)publicvoidb(String s){TestUser testUser = testUserMapper.selectById(1L);System.out.println(testUser);
testUserMapper.updateUserById("b",2L);if(s.equals("b")){int a =1/0;}}// @Transactional(rollbackFor =Exception.class)publicvoidc(String s){TestUser testUser = testUserMapper.selectById(2L);System.out.println(testUser);
testUserMapper.updateUserById("c",3L);if(s.equals("c")){int a =1/0;}}}
@MapperpublicinterfaceTestUserMapperextendsBaseMapper<TestUser>{@Update("update testUser set name = #{name} where id=#{id}")publicintupdateUserById(String name,Long id );}
1.1 如果A加@Transactional注解,B不加@Transactional注解:
param=b,请求后结果如下:
注意
:问:根据idea控制台可看出,b方法进行查询了id=1的记录,为什么name=a而不是name=1呢?。
答:此时查询出来的记录name=a了 ,是因为b在a的事务中了,
同一事务内数据是可见共享的
,而此时的name=a并未落到数据库中。
为了验证此处
,可以在a方法最后添加一个Thread.Sleep(10000)睡眠10s,在这10s中调用接口传参param=a,,去navicat查询数据库中id=1的这条记录,查看是否为1,若为1则证明我上述是正确的!
原因分析:
提示:由此可见,b和c方法加入了a方法的主事务中,即三个方法在同一个事务中:
解析:在param=b调用接口后,可以直观的看到,
数据都变成了初始值
,即回滚了。(其实b方法加不加@Transional注解,事务都会生效即两个方法都在同一个事务中,博主亲测)
1.2 如果update方法不加@Transactional注解,B加@Transactional注解:
代码,更改如下:
param=b,请求后结果如下:
注意
提问:根据idea控制台可看出,b方法进行查询了id=1的记录,
此时name=a和上方1.1标题的name=a有什么区别吗
?。
答:此时查询出来的记录name=a ,是因为update方法的更改已经落到数据库了,与事务没有关系。
为了验证此处
,可以在a方法最后添加一个Thread.Sleep(10000)睡眠10s,在这10s中调用接口传参param=a,,去navicat查询数据库中id=1的这条记录,查看name是否为a,若10s内name=a则证明我上述是正确的!
1.3 如果update方法不加@Transactional注解,B加@Transactional注解,并且使用代理类调用B方法:
代码,更改如下:
param=b,请求后结果如下:
如图
结论:本类中,当一个没有开启事务的方法A:
1.直接调用开启了事务的方法B,AB都不会开启事务。
2.但是如果A方法使用代理对象进行B方法的调用,那么B方法还是会开启事务的,但是A方法依然没有事务。
场景一的总结分析:
解析:
情景1.2 当param=b的时候,可以观察到id=1,id=2的记录
并未回滚
。由此可见,
update方法没有开启事务,b方法的事务也失效了。
情景1.3 当param=b的时候,可以观察到id=1,的记录
并未回滚
。id=2的记录 进行了数据回滚。由此可见,
A方法没有开启事务,B方法的开启了事务。
问: 为什么会出现上述情况呢?
答: 因为本类中,A方法直接进行B方法的调用相当于使用this.b(),具体看下方贴图class编译文件。这里涉及到@Transitional的事务原理是基于AOP实现的,所以要想事务生效,必须通过代理对象进行方法的调用,this调用相当于原生对象调用(未被事务代理进行方法增强)
备注:我的开发环境是
springboot 2.3版本,AOP默认代理是Cglib
,可以进行代理对象直接调用本类方法,无需implements接口,也可以直接进行ServiceImpl的强制类型转换。(如果是jdk代理会报错)当然,也可以使用
自己注入自己
的方式,进行B方法的调用,但是要通过Implements的方式。
场景二 A和B方法不在同一个类中:
注意:
TestUserServiceImpl的updateOther 就是A方法
GoodsServiceImpl的updateGoodsById 就是B方法
我们先自己建立一个另一个操作类,用于观察对比方法对应的事务具体情况(事务传播等级)。代码如下
@ServicepublicclassTestUserServiceImplimplementsITestService{@AutowiredprivateTestUserMapper testUserMapper;@AutowiredprivateIGoodsService goodsService;@Override@Transactional(rollbackFor =Exception.class)publicintupdateOther(String param)throwsInterruptedException{// 该testUser表记录,修改前 name = '1'
testUserMapper.updateUserById("a",1L);// 该goods表记录,修改前 name = '1'
goodsService.updateGoodsById("product1",1L);return1;}}
@ServicepublicclassGoodsServiceImplimplementsIGoodsService{@AutowiredprivateGoodsMapper goodsMapper;@AutowiredprivateTestUserMapper testUserMapper;// @Transactional(rollbackFor = Exception.class)@OverridepublicintupdateGoodsById(String name,Long id){TestUser testUser = testUserMapper.selectById(1L);System.out.println("GoodsServiceImpl.updateGoodsById()查询的user结果: "+ testUser.toString());
goodsMapper.updateGoodsById(name, id);// 修改前 name = '1'int i =1/0;return0;}}
@MapperpublicinterfaceGoodsMapperextendsBaseMapper<Goods>{@Update("update goods set name = #{name} where id=#{id}")publicintupdateGoodsById(String name,Long id );}
2.1 如果A方法加@Transactional注解,b方法不加@Transactional注解:
请求后结果如下:
2.1的现象分析:param=b的时候,mysql中的数据都未改变,说明两者都进行了事务数据回滚,说明二者都开启了事务。并且b方法中查询到了a方法的数据改变,说明二者是同一个事务。
结论:当不同类方法调用的时候(A调用B),若A方法添加了@Transional,B方法没有添加@Transional,那么B方法会加入到A方法的事务中。
(若B方法添加了@Transional,AB方法也在同一事务中,因为spring的事务默认传播等级)
2.2 如果A方法不加@Transactional注解,B加@Transactional注解:
请求后结果如下:
注意
提问:根据idea控制台可看出,b方法进行查询了id=1的记录,
此时name=a和上方2.1标题的name=a有什么区别吗
?。
答:
2.2场景 b方法查询的name=a是因为A方法没有开启事务,数据update更改后直接落库了,此时查询出来的name=a,是从数据库中读取的。
2.1场景 b方法查询的name=a是因为二者在同一事务中,数据共享的,好像是什么快照读(具体忘记了,mvcc的什么东西)。
2.2的现象分析:因为在b方法中出现了异常,mysql中只有goods表进行了数据回滚,说明只有B方法开启了事务。
结论:在不同类中,A不添加@Transitional方法调用B添加@Transitional的方法,A不会开启事务,B会开启事务。
————————————————
版权声明:本文开头部分为借鉴下方链接
原文链接:https://blog.csdn.net/u012279452/article/details/126505417
版权归原作者 吴巴格 所有, 如有侵权,请联系我们删除。