聊聊spring事务失效的12种场景,太坑了
前言
对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了。
在某些业务场景下,如果一个请求中,需要同时写入多张表的数据。为了保证操作的原子性(要么同时成功,要么同时失败),避免数据不一致的情况,我们一般都会用到spring事务。
确实,spring事务用起来贼爽,就用一个简单的注解: @Transactional ,就能轻松搞定事务。我猜大部分小伙伴也是这样用的,而且一直用一直爽。
但如果你使用不当,它也会坑你于无形。
今天我们就一起聊聊,事务失效的一些场景,说不定你已经中招了。不信,让我们一起看看。
一 事务不生效1.访问权限问题
众所周知,java的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。
但如果我们在开发过程中,把有某些事务方法,定义了错误的访问权限,就会导致事务功能出问题,例如: @Service public class UserService { @Transactional private void add(UserModel userModel) { saveData(userModel); updateData(userModel); } }
我们可以看到add方法的访问权限被定义成了 private ,这样会导致事务失效,spring要求被代理方法必须是public 的。
说白了,在 AbstractFallbackTransactionAttributeSource 类的computeTransactionAttribute 方法中有个判断,如果目标方法不是public,则TransactionAttribute 返回null,即不支持事务。 protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { // Don"t allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } // The method may be on an interface, but we need attributes from the target class. // If the target class is null, the method will be unchanged. Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); // First try is the method in the target class. TransactionAttribute txAttr = findTransactionAttribute(specificMethod); if (txAttr != null) { return txAttr; } // Second try is the transaction attribute on the target class. txAttr = findTransactionAttribute(specificMethod.getDeclaringClass()); if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { return txAttr; } if (specificMethod != method) { // Fallback is to look at the original method. txAttr = findTransactionAttribute(method); if (txAttr != null) { return txAttr; } // Last fallback is the class of the original method. txAttr = findTransactionAttribute(method.getDeclaringClass()); if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { return txAttr; } } return null; }
也就是说,如果我们自定义的事务方法(即目标方法),它的访问权限不是 public ,而是private、default或protected的话,spring则不会提供事务功能。 2. 方法用final修饰
有时候,某个方法不想被子类重新,这时可以将该方法定义成final的。普通方法这样定义是没问题的,但如果将事务方法定义成final,例如: @Service public class UserService { @Transactional public final void add(UserModel userModel){ saveData(userModel); updateData(userModel); } }
我们可以看到add方法被定义成了 final 的,这样会导致事务失效。
为什么?
如果你看过spring事务的源码,可能会知道spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
注意:如果某个方法是static的,同样无法通过动态代理,变成事务方法。 3.方法内部调用
有时候我们需要在某个Service类的某个方法中,调用另外一个事务方法,比如: @Service public class UserService { @Autowired private UserMapper userMapper; @Transactional public void add(UserModel userModel) { userMapper.insertUser(userModel); updateStatus(userModel); } @Transactional public void updateStatus(UserModel userModel) { doSameThing(); } }
我们看到在事务方法add中,直接调用事务方法updateStatus。从前面介绍的内容可以知道,updateStatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateStatus方法不会生成事务。
由此可见,在同一个类中的方法直接内部调用,会导致事务失效。
那么问题来了,如果有些场景,确实想在同一个类的某个方法中,调用它自己的另外一个方法,该怎么办呢? 3.1 新加一个Service方法
这个方法非常简单,只需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。具体代码如下: @Servcie public class ServiceA { @Autowired prvate ServiceB serviceB; public void save(User user) { queryData1(); queryData2(); serviceB.doSave(user); } } @Servcie public class ServiceB { @Transactional(rollbackFor=Exception.class) public void doSave(User user) { addData1(); updateData2(); } } 3.2 在该Service类中注入自己
如果不想再新加一个Service类,在该Service类中注入自己也是一种选择。具体代码如下: @Servcie public class ServiceA { @Autowired prvate ServiceA serviceA; public void save(User user) { queryData1(); queryData2(); serviceA.doSave(user); } @Transactional(rollbackFor=Exception.class) public void doSave(User user) { addData1(); updateData2(); } }
可能有些人可能会有这样的疑问:这种做法会不会出现循环依赖问题?
答案:不会。
其实spring ioc内部的三级缓存保证了它,不会出现循环依赖问题。但有些坑,如果你想进一步了解循环依赖问题,可以看看我之前文章《spring:我是如何解决循环依赖的?》。 3.3 通过AopContent类
在该Service类中使用AopContext.currentProxy()获取代理对象
上面的方法2确实可以解决问题,但是代码看起来并不直观,还可以通过在该Service类中使用AOPProxy获取代理对象,实现相同的功能。具体代码如下: @Servcie public class ServiceA { public void save(User user) { queryData1(); queryData2(); ((ServiceA)AopContext.currentProxy()).doSave(user); } @Transactional(rollbackFor=Exception.class) public void doSave(User user) { addData1(); updateData2(); } } 4.未被spring管理
在我们平时开发过程中,有个细节很容易被忽略。即使用spring事务的前提是:对象要被spring管理,需要创建bean实例。
通常情况下,我们通过@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。
当然创建bean实例的方法还有很多,有兴趣的小伙伴可以看看我之前写的另一篇文章《@Autowired的这些骚操作,你都知道吗?》
如果有一天,你匆匆忙忙的开发了一个Service类,但忘了加@Service注解,比如: //@Service public class UserService { @Transactional public void add(UserModel userModel) { saveData(userModel); updateData(userModel); } }
从上面的例子,我们可以看到UserService类没有加 @Service 注解,那么该类不会交给spring管理,所以它的add方法也不会生成事务。 5.多线程调用
在实际项目开发中,多线程的使用场景还是挺多的。如果spring事务用在多线程场景中,会有问题吗? @Slf4j @Service public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); new Thread(() -> { roleService.doOtherThing(); }).start(); } } @Service public class RoleService { @Transactional public void doOtherThing() { System.out.println("保存role表数据"); } }
从上面的例子中,我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。
如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。 private static final ThreadLocal
在影视公司投资的电影份额可以退吗?感觉是被骗了该怎么退款?目前几乎99电影投资的相关诈骗陷阱可能都源于互联网,各种社交平台成为推手和始作俑者,只要打开这些平台搜索引擎,输入电影投资,一定会弹出大量诈骗信息。网上投资电影被骗真实案例分享去年
网上众筹投资电影被骗了,我该怎么追回损失?维权有用吗?随着国产电影越来越火,票房轻松过亿的电影一部接着一部,因此让无数人看到了美好的钱景,但遗憾的是,很多人无奈生财无道。如果此时,在不经意之中,你认识了一个电影行业的人,带你投资电影,
程序员的摸鱼终极解决方案终极摸鱼神器用到了深度学习算法,作者训练了一个人脸识别模型,可以判断摄像头捕捉的人是不是你的老大,如果是老大程序会自动帮你回到工作状态。1。老板站起来,他正在接近你2。当他靠近时,
装X神器!NuShell今天我要给大家介绍一个生产力工具(装逼神器)Shell,它叫Nushell,它是用Rust写的,安全性提高的同时,Bug率也降低了,NuShell专注于实现以下目标创建具有现代感的
IDEA学习之高级断点调试技巧本文介绍IDEA编辑器的调试功能,主要包括断点调试单点调试高级调试。可以收藏本文章,使用到的时候方便查找。1。设置断点选定要设置断点的代码行,在行号的区域后面单击鼠标左键即可。2。
源码探秘Tomcat在SpringBoot中是如何启动的?前言1从Main方法说起2走进Tomcat内部3总结4前言我们知道SpringBoot给我们带来了一个全新的开发体验,我们可以直接把web程序达成jar包,直接启动,这就得益于Sp
用代码记住家里面的WiFi密码老王,你家的WiFi账号密码是什么呀?你慢悠悠的接过手机,找到WiFi设置,选择你家里的WiFi,准备输入密码。你愣了一会儿,一拍脑袋说我草,只记得名字不知道密码了。这个场景在你生
3。2万Star的算法可视化项目火了算法的重要性不言而喻啦,每次求职之前刷LeetCode刷的头昏脑胀,大厂笔试时看到Hard级的算法题瞬间头皮发麻。算法是很重要,问题是如何更好的学习好算法才是关键。一提到算法,大部
盘点7月份yyds的开源项目本文盘点了7月份比较火的几个开源项目,涉及JavaPython教程等,这些开源项目分别是1。主流技术栈原理2。数据可视化分析工具3。Spring手撸专栏4。接口管理平台5。直播源厂
Github曾经登顶热榜Top1的女装项目现状大家晚上好呀!嘿嘿!相信大部分逛Github的同学一定听过一个女装大佬的项目。这个仓库已经有接近4年的历史了!曾经,这个项目一度冲上了Github热榜第一!让我们来看看这个项目的现
这个GitHub项目登上了热榜!带领大家阅读Spring源码Spring框架是Java后端开发必不可少的框架技术,但是大多数开发者仅仅停留在会用的阶段。对于背后的机制如何,大多数人无法说出来。但Spring技术还是面试常问的主题,它背后的机