代码位于:
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
中
也就是说,默认情况下你无法使用@Transactional
对一个非public的方法进行事务管理
**解决方案:**修改需要事务管理的方法为public
。
- 出现了自调用。什么是自调用呢?我们看个例子
@Service
public class DmzService {
public void saveAB(A a, B b) {
saveA(a);
saveB(b);
}
@Transactional
public void saveA(A a) {
dao.saveA(a);
}
@Transactional
public void saveB(B b){
dao.saveB(a);
}
}
上面三个方法都在同一个类DmzService
中,其中saveAB
方法中调用了本类中的saveA
跟saveB
方法,这就是自调用。在上面的例子中saveA
跟saveB
上的事务会失效
那么自调用为什么会导致事务失效呢?我们知道Spring中事务的实现是依赖于AOP
的,当容器在创建dmzService
这个Bean时,发现这个类中存在了被@Transactional
标注的方法(修饰符为public)那么就需要为这个类创建一个代理对象并放入到容器中,创建的代理对象等价于下面这个类
public class DmzServiceProxy {
private DmzService dmzService;
public DmzServiceProxy(DmzService dmzService) {
this.dmzService = dmzService;
}
public void saveAB(A a, B b) {
dmzService.saveAB(a, b);
}
public void saveA(A a) {
try {
// 开启事务
startTransaction();
dmzService.saveA(a);
} catch (Exception e) {
// 出现异常回滚事务
rollbackTransaction();
}
// 提交事务
commitTransaction();
}
public void saveB(B b) {
try {
// 开启事务
startTransaction();
dmzService.saveB(b);
} catch (Exception e) {
// 出现异常回滚事务
rollbackTransaction();
}
// 提交事务
commitTransaction();
}
}
上面是一段伪代码,通过startTransaction
、rollbackTransaction
、commitTransaction
这三个方法模拟代理类实现的逻辑。因为目标类DmzService
中的saveA
跟saveB
方法上存在@Transactional
注解,所以会对这两个方法进行拦截并嵌入事务管理的逻辑,同时saveAB
方法上没有@Transactional
,相当于代理类直接调用了目标类中的方法。
我们会发现当通过代理类调用saveAB
时整个方法的调用链如下:
实际上我们在调用saveA
跟saveB
时调用的是目标类中的方法,这种清空下,事务当然会失效。
常见的自调用导致的事务失效还有一个例子,如下:
@Service
public class DmzService {
@Transactional
public void save(A a, B b) {
saveB(b);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveB(B b){
dao.saveB(a);
}
}
当我们调用save
方法时,我们预期的执行流程是这样的
也就是说两个事务之间互不干扰,每个事务都有自己的开启、回滚、提交操作。
但根据之前的分析我们知道,实际上在调用saveB方法时,是直接调用的目标类中的saveB方法,在saveB方法前后并不会有事务的开启或者提交、回滚等操作,实际的流程是下面这样的
由于saveB方法实际上是由dmzService也就是目标类自己调用的,所以在saveB方法的前后并不会执行事务的相关操作。这也是自调用带来问题的根本原因:自调用时,调用的是目标类中的方法而不是代理类中的方法
解决方案:
- 自己注入自己,然后显示的调用,例如:
@Service
public class DmzService {
// 自己注入自己
@Autowired
DmzService dmzService;
@Transactional
public void save(A a, B b) {
dmzService.saveB(b);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveB(B b){
dao.saveB(a);
}
}
这种方案看起来不是很优雅
- 利用
AopContext
,如下:
@Service
public class DmzService {
@Transactional
public void save(A a, B b) {
((DmzService) AopContext.currentProxy()).saveB(b);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveB(B b){
dao.saveB(a);
}
}
使用上面这种解决方案需要注意的是,需要在配置类上新增一个配置
// exposeProxy=true代表将代理类放入到线程上下文中,默认是false
@EnableAspectJAutoProxy(exposeProxy = true)
个人比较喜欢的是第二种方式
这里我们做个来做个小总结
总结
一图胜千言
事务回滚相关问题
回滚相关的问题可以被总结为两句话
- 想回滚的时候事务确提交了
- 想提交的时候被标记成只能回滚了(rollback only)
先看第一种情况:想回滚的时候事务确提交了。这种情况往往是程序员对Spring中事务的rollbackFor
属性不够了解导致的。
Spring默认抛出了未检查
unchecked
异常(继承自RuntimeException
的异常)或者Error
才回滚事务;其他异常不会触发回滚事务,已经执行的SQL会提交掉。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor
属性。
对应代码其实我们上篇文章也分析过了,如下:
以上代码位于:
TransactionAspectSupport#completeTransactionAfterThrowing
方法中
默认情况下,只有出现RuntimeException
或者Error
才会回滚
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
所以,如果你想在出现了非RuntimeException
或者Error
时也回滚,请指定回滚时的异常,例如:
@Transactional(rollbackFor = Exception.class)
第二种情况:想提交的时候被标记成只能回滚了(rollback only)。
对应的异常信息如下:
Transaction rolled back because it has been marked as rollback-only
我们先来看个例子吧
@Service
public class DmzService {
@Autowired
IndexService indexService;
@Transactional
public void testRollbackOnly() {
try {
indexService.a();
} catch (ClassNotFoundException e) {
System.out.println(“catch”);
}
}
}
@Service
public class IndexService {
@Transactional(rollbackFor = Exception.class)
public void a() throws ClassNotFoundException{
// …
throw new ClassNotFoundException();
}
}
在上面这个例子中,DmzService
的testRollbackOnly
方法跟IndexService
的a
方法都开启了事务,并且事务的传播级别为required
,所以当我们在testRollbackOnly
中调用IndexService
的a
方法时这两个方法应当是共用的一个事务。按照这种思路,虽然IndexService
的a
方法抛出了异常,但是我们在testRollbackOnly
将异常捕获了,那么这个事务应该是可以正常提交的,为什么会抛出异常呢?
如果你看过我之前的源码分析的文章应该知道,在处理回滚时有这么一段代码
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://img-blog.csdnimg.cn/img_convert/285f3a7407cbcef27aba61a44bbe3423.jpeg)
最后
我还通过一些渠道整理了一些大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。
还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
望能帮助到大家。**
[外链图片转存中…(img-IJqt2FLB-1713525229290)]
还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。
[外链图片转存中…(img-1GzZXR0D-1713525229290)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!