文章可能会动态更新 保证结论不误导人 拒绝复制粘贴
1.第一种情况 :调用方加上@Transactional 注解,被调用方未加上注解 (同一个service类)
Exp:
@Transactional
void methodA(){
// insert table1
methodB(Object obj);
}
void methodB(){
// insert table2
}
结论:methodB()出现异常,抛给methodA,A中捕获到异常,进行回滚处理,table1,table2都未插入
2.第二种情况:调用方未加上注解,被调用方加上注解(同一个service类)
Exp:
void methodA(){
// insert table1
methodB(Object obj);
}
@Transactional
void methodB(){
// insert table2
}
结论: methodB()非事务执行。spring的aop proxy 代理对象执行methodA(),由于methodA()上面没有注解标记,所以不会开启事务 , 所以不管A方法里面写什么 ,都不能回滚,table1插入 table2未插入。
基于上述结论的理解
3.第三种情况:调用方未加上注解,被调用方加上注解(不同service类)
结论:此时被调用方在另一个类BserviceImpl,该类代理对象bProxy调用methodB()时 是有注解的 能开启事务,所以出现异常都会回滚 table1,table2都未插入。
拓展:
存在业务逻辑: 需要批量插入员工信息至公司表与部门表,员工信息要么同时在公司、部门表,要么同时不存在。
因此需要将 insert t_company 和 insert t_dept 进行事务管理,当第二个员工插入部门表出现异常时,第一个已正常插入的员工不能受到影响,第二个员工的信息需要回滚。
此时调用方 methodA() 上面肯定不能加上注解 ,如果将methodB(){ insert t_company,insert t_dept}写在同一个service中,并加上注解,就会出现上述第二种情况,回滚失败,第二个员工的信息存在公司表 而不存在部门表。
解决方式:1.写在另一个service 2.写在同一个service也可以,获取代理对象 通过代理对象调用 methodB() 这样就能执行回滚了(推荐), 方式较多 可以@Autowired一遍自己 (推荐) 或者用下述方法 ,具体原理 在下面超链接的那篇文章有解释。
// 需要先在启动类加上注解 @EnableAspectJAutoProxy(exposeProxy = true)
YourServiceImpl yourService = (YourServiceImpl ) AopContext.currentProxy();
yourService.methodB();
拓展:
之前有评论 我回复了 但是因为那条回复不够严谨 可能造成误导 现在已经将评论删除 请见谅;
在通过对aop有了稍微更深入一点点的学习后 ,
(如果有看到这篇文章感到困惑的朋友 可能需要参见我的另一篇文章 spring工作流程 )
突然发现以前的认知不够完善 , 上述结论仍然是正确的 但感觉不够深入 所以进行补充。
- 可以看到 我们在testB方法中 加上了注解 传播级别设置为never ,可是并没有生效 ,也就是说 这个注解相当于没写,对的 没听错
- 我们换用bean(该场景下为代理对象)来调用,可以看到达到了我们预期的效果
- 那么问题来了 我们知道事务开启一定是在代理对象执行的,那么下述情况 为什么还会回滚呢
注意:我们看到即使是写了注解 传播机制为never也未生效 ,且这只是在同一个对象内执行的 , 所以仍然能回滚的原因 不能用事务传播机制默认为自动加入去理解 这是个初学者(没错 就是初学时的我)容易犯的错误
下面用伪代码分析代理到底做了什么:
class proxy extends TransactionalController{
/**
* bean对象 注意现在正在准备执行的步骤就是动态代理 所以这个bean是指代理前的bean
*/
private TransactionalController transactionalController;
/**
* 代理testA方法
* @author qkj
*/
public void testA(){
// 事务开启
conn.start;
try {
// 被代理的testA方法
transactionalController.testA()->{
// 里面包含了testB方法
transactionalController.testB();
};
// 所以即使testB方法中 出现异常 也能捕获 并回滚
}catch (Exception e){
conn.rockback;
}
conn.commit;
}
}
事务的传播机制
既然上面那种情形 不是传播机制 那么传播机制到底怎么体现呢?
我们从REQUIRES_NEW 分析 先看看复制粘贴的话是怎么描述的:
. 粘贴1:如果当前存在事务,就把当前事务挂起;如果当前方法没有事务,就新建事务;
. 粘贴2:不管是否存在事务,业务方法总会自己开启一个事务,如果在已有事务的环境中调用,
已有事务会被挂起,新的事务会被创建,直到业务方法调用结束,已有事务才被唤醒
第二句会比第一句稍微好理解一点点 但还是有些抽象 咱也不知道网上复制一大堆 这些话他们到底懂不懂.
模拟一种可能存在的业务,结合业务理解会比较容易:
运行结果:
数据库结果:
摸鱼人员表:
绩效记录表:
这样符合实际业务,摸鱼被抓了 绩效该扣还是得扣 不会因为人员名单插入异常就全部回滚了,
这里因为绩效业务事务传播机制是Propagation.REQUIRES_NEW,外层摸鱼业务有事务,当执行到绩效时,绩效开启一个新的事务 并将旧的事务挂起,直到绩效事务提交后,旧事务继续,这样就保证了绩效是个独立的事务。有个这个分析,其它的也就比较容易理解了。