您的当前位置:首页 >数据库 >事务篇:Spring事务的坑,你都踩过吗? 正文

事务篇:Spring事务的坑,你都踩过吗?

时间:2025-11-05 09:27:54 来源:网络整理编辑:数据库

核心提示

本篇,我们将要从本人以及同事在工作中踩过的关于事务的坑,以及踩坑之后自己在发现的使用 Spring 事务存在的坑展示给大家,让大家也避免踩坑。一、来看看这些事务之坑总得来说呢,经常遇到的其实是这四类:

本篇,事务g事我们将要从本人以及同事在工作中踩过的坑都关于事务的坑,以及踩坑之后自己在发现的踩过使用 Spring 事务存在的坑展示给大家,让大家也避免踩坑。事务g事

一、坑都来看看这些事务之坑

总得来说呢,踩过经常遇到的事务g事其实是这四类:自身调用、异常被吃、坑都异常抛出类型不对以及事务的踩过传播机制不熟悉。

具体例子,事务g事我们来看看:

1.数据库引擎不支持事务

感觉这种一般估计不太会出现。坑都毕竟你要使用事务,踩过肯定会在最开始就选择支持事务的事务g事数据库引擎咯。

比如常用的坑都 oracle 直接就是支持事务的,而 mysql 的踩过 innodb 支持事务,myIsam 的话,是不支持事务的。

在 mysql5.1 版本之前,默认引擎是b2b供应网 myIsam ,而之后的版本则默认就是innodb 了~

建议检查项:mysql的数据库引擎。

执行命令:

复制show variables like %storage_engine%;1.

我们看到,我本地的 5.7 版本 的 mysql 数据库的数据库引擎默认就是 innodb。

2.方法不是 public 的

其实经过本人的测试,除了 private 方法本身就不能编译通过以外,public、protected 以及 default 三个修饰符都是支持事务的。

有兴趣,你也可以测试一下!

3.自身调用问题

比如在同一个类中的两个方法 methodA 和 methodB 。methodA 没有设置事务,methodB 设置了事务,methodA 调用 methodB 时,事务便会失效。

1) 同一个类中的方法调用 复制/** * 自调用测试:事务失效,表中新增了两条数据:id为10和11的数据 */

@Override

public void testInvokeBInOneClass(){ User user = User.builder().id(10).name("王二").age(22).build(); userDao.addUser(user); testB();}

@Transactional

public void testB(){ User user = User.builder().id(11).name("张三").age(22).build(); userDao.addUser(user); int i = 1/0;}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.

我们预测一下:

若事务失效,数据库中将会成功增加两条数据:王二和张三。

若事务生效,WordPress模板则表中将不会增加任何数据。

执行该方法后,我们会发现,数据库的 t_user2 表中的记录为:

没错,结果,事务失效了。

2)不同类中的方法调用

我们把 testB () 方法放到另一个类 TransactionBImpl 中。

此时,调用 TransactionBImpl 类中的 testB () 方法;

复制/** * 自调用测试:事务生效,表中新增了一条数据:id为10的数据 */

@Override

public void testInvokeBInTwoClass(){ User user = User.builder().id(10).name("王二").age(22).build(); userDao.addUser(user); transactionB.testB();}1.2.3.4.5.6.7.8.9.

发现数据库中的数据为:

说明事务生效了,为什么呢?

因为外层 testInvokeBInTwoClass() 方法本身是没有事务(没有加事务注解)的,它调用了另一个类中 加了事务注解的 testB() 方法,不要忘记 @Transactional 注解的默认传播机制,是PROPAGATION_REQUIRED - 若不存在事务,就要自己创建一个新事务。

也就是说,最终的效果就是,testB() 方法内部在一个事务内,testInvokeBInTwoClass()方法中,高防服务器并没有事务(不会因为异常而触发回滚操作)。

那么,最终的结果,也就轻易理解咯~

如果你听过独立事务的话,就能想到它的实现机制了吧!

Tips

有些业务需要,要求 methodA 调用 methodB 时,并不会因为 methodB 的执行失败,而影响了调用之前的操作。如在在表中调用之前登记了一条状态日志,此时并不想要因为调用失败,而回滚了这条记录,就可以这样操作啦~

小结

事务在发生自调用时,若调用方没有加 @Transactional 注解,事务便会失效。

若要使事务生效,则可以考虑将该被调用的方法放在另一个类中即可。

4.不支持事务

这种情况比较容易理解,只是会在编码过程中容易被忽略掉,所以在这里也提一下。

当 methodA 调用另一个类中的 methodB ,若 methodB 设置了事务的传播机制为Propagation.NOT_SUPPORTED。

那么,即使 methodA 开启了事务,也不一定会按照自己的预期来发展的,来看看下面这个例子:

UserServiceImpl 类

复制 @Override

public void testNotSupported() { User user = User.builder().id(10).name("王二").age(22).build(); userDao.addUser(user); transactionB.testNotSupported(); }1.2.3.4.5.6.

TransactionBImpl 类

复制@Transactional

(propagation = Propagation.NOT_SUPPORTED)

@Override

public void testNotSupported(){ User user = User.builder().id(11).name("张三").age(22).build(); userDao.addUser(user); int i = 1/0; }1.2.3.4.5.6.7.8.

即,UserServiceImpl 类中的 testNotSupported()方法调用了 TransactionBImpl 类 中的 testNotSupported()方法。

我们来分析一下,按照调用方是否开启事务,可以分为以下两种情况 :

1)若调用方 testNotSupported()方法不加 @Transactional 注解,则表中数据为:

显而易见,说明两个方法统一都没有事务。

若加上,则只插入了一条数据。

说明外部方法还是存在事务的,只要出现异常就会回滚。而被调用方 transactionB.testNotSupported() 的方法内部不支持事务,于是该方法出错之后也不会出现事务回滚,因此出错之前的插表操作就没有回滚。

5.异常被catch住了,没有抛出来

由于事务默认回滚的是:RuntimeException 和 Error 两种情况,所以以下两种情况都会失效。

1)异常被吃了,事务失效 复制/** * 7、异常被吃了:try掉异常(未抛出),事务失效 */

@Transactional

@Override

public void testException(){ try { User user = User.builder().id(10).name("王二").age(22).build(); userDao.addUser(user); int i = 1/0; }catch (Exception e) { System.out.println("执行失败:"+e.getMessage());// throw new RuntimeException("执行失败,抛出异常:"+e.getMessage()); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.

也就是说,异常并没有被抛出来,而是通过 catch 住,然后做了一些其他的逻辑处理,这种事务是不会生效的。

再来看看第二种情况。

2)抛出Exception异常,事务失效

复制@Transactional

@Override

public void testException() throws Exception { try { User user = User.builder().id(10).name("王二").age(22).build(); userDao.addUser(user); int i = 1/0; }catch (Exception e) { System.out.println("执行失败:"+e.getMessage()); throw new Exception("抛出了Exception异常:"+e.getMessage());// throw new RuntimeException("执行失败,抛出异常:"+e.getMessage()); } }1.2.3.4.5.6.7.8.9.10.11.12.13.

回想一下我们的大前提:Spring事务默认回滚的是:RuntimeException和Error两种情况。现在抛出了 Excption ,就不会触发事务的回滚,所以这样事务也是不生效的。

要怎样才能让这样的事务生效呢?

改成抛出 RuntimeException 事务就生效啦~ 你完全可以现在就试试。

对了,如果你想触发其他异常的回滚,包括你自己定义的异常或者 Exception 异常的话,也不是没有办法。只需要在方法的注解上配置一下 rollbackFor 属性即可,如:@Transactional(rollbackFor = Exception.class)。

留一个思考题给你:若配置了其他异常,那原本的规则是否被覆盖掉?

小结

只要抓住一点:事务默认在:RuntimeException 和 Error 两种情况下执行回滚操作。

因此,

1)异常被捕获掉,没有抛出来,就不会生效。

2)抛出的 RuntimeException 异常或者未遇到 Error ,事务默认也不会生效的。

那么,怎么处理才能让事务生效,想必已经很明显了吧?

6.未启用spring事务管理功能

@EnableTransactionManagement 注解用来启用spring事务自动管理事务的功能,只有有这个注解,这个注解千万不要忘记写了。

但是当引入了;

复制spring-boot-starter-jdbc1.

就可以不用我们自己写,为什么呢?我们来看看;

@EnableTransactionManagement 这个注解开启事务,其实和我们自己使用@EnableTransactionManagement是一样的 因此,只要我们在 SpringBoot 中引入了 spring-boot-starter-jdbc 这个依赖以后,我们就只需要使用 @Transactional 就可以了。

二、总而言之

好了,本篇文章,接着上一篇的事务基础,为大家演示了几个开发过程中容易出现的事务失效,或者事务不能按照自己的预期来执行的几种场景。

总结一下,日常中最容易出现事务失效或者不能按照预期执行的情况,大致分为四类:自身调用、异常被吃、异常抛出类型不对以及事务的传播机制不熟悉。

那么我们需要如何去避免踩坑,正确高效地使用事务呢?

很简单,只需要关注单个方法时事务的回滚机制,以及涉及到两个以及两个以上方法的调用时事务的传播机制以及Spring事务的原理。

单个方法的调用,事务只会在执行过程中出现 RuntimeException 和 Error 以及事务超时时进行事务的回滚;多个方法:当在同一个类中进行方法调用时,若要事务不失效,则需要在调用方的方法都加上事务注解,同时需要关注事务的传播机制以及各层方法的事务回滚情况;

不在同一类中时,则需要根据特定的业务场景,选择不同的传播机制。

下一篇:Google最近发布了首批能在Chrome OS本地运行的安卓应用集,通过‘安卓运行时’扩展完成了该壮举。现在,一位开发者已经指明了将安卓应用带入桌面版Chrome的路。弗拉德·菲利波夫的chromeos-apk脚本和ARChon安卓运行时扩展手拉手一起开展工作,将安卓应用带进了Windows,Mac和Linux桌面上的Chrome中。运行在Ubuntu 14.04 LTS上的安卓应用:IMDB,Flipboard和Twitter通过‘安卓运行时’运行的应用的性能不是很令人惊异,任何想要运行Dead Trigger 2或者其它图形密集型游戏的雄心壮志可以放到一边了。同样地,要运行官方的‘安卓运行时’的非官方重构包,或者在Chrome OS之外运行的话,其系统完整性(如网络摄像头,扬声器等)可能不完整或者根本不可能。按照下面的指南一步步来,并不保证一定成功。它只能作为高度实验性进行,里面遍布漏洞,很不稳定——甚至白日见鬼。只能出于好奇而尝试,不要高度寄予厚望,这样你就不会深受其困扰。安卓应用转战Linux大法 要通过Chrome在Linux上运行安卓应用,很明显,你需要安装Chrome,要求的版本是37,或者更高。坦率地讲,假如你打算玩玩不稳定的Chrome版本,那么你也可以下载并为Linux安装。假如已经安装了Chrome的某个版本?你可以通过命令行来安装不稳定版,命令如下:sudo apt-get install google-chrome-unstable有了 Chrome 之后,你需要下载定制版的‘安卓运行时’扩展,而不是Google或Chronium提供的版本——由弗拉德·菲利波夫创建的‘安卓运行时’。这个版本和官方的有着诸多的不同,最突出的不同就是它可以运行在桌面版的浏览器上。从BitBucket下载ARChon v1.0下载好‘安卓运行时’扩展后,你需要从.zip解压内容,并移动解压后的文件夹到你的Home文件夹。要安装它,打开Google Chrome,点击汉堡式菜单按钮,然后导航到扩展页。检查‘启用开发者模式’并点击‘加载解包的扩展’按钮。‘安卓运行时’扩展本身不会做太多事情,所以你需要从安卓应用创建兼容包。要完成这项工作,你需要‘chromeos-apk’命令行Javascript工具,它可以从“Node 封装模块管理器(npm )”安装。首先运行:sudo apt-get install npm nodejs nodejs-legacyUbuntu 64位用户?你还需要获取以下库:sudo apt-get install lib32stdc++6现在,运行npm命令来安装该脚本吧:npm install -g chromeos-apk根据你的配置,你可能需要过会儿使用sudo来运行。假如你不喜欢通过sudo安装npm模块,你可以试着骗过它。现在,搞定了。去Google找找你想要试试的应用的APK吧,请牢记不是所有的安卓应用都会工作,而那些可以工作的也未必工作得很好,或者缺少功能。把你想要的安卓APK放到~/Home,然后回到终端中使用以下命令来转换,你可以将APK命名成任何你想要的名字:chromeos-apk replaceme.apk --archon该命令将花一点时间来完成这项工作,也许也就是一眨眼的时间。实际上,不需要眨眼的时间现在,在你的Home文件夹内有个ARChon生成的Chrome APK extension-y folder-y这样的东西。所有剩下来要做的事,就是安装并查看它是否正常工作!回到chrome://extensions页面,再次轻敲‘加载解封装扩展’按钮,但这次选择上面脚本创建出来的文件夹。应用应该会正确安装,但是它确实会没有问题吗?打开Chrome应用启动器或应用页面并启动它来看看是否有问题。由于ARChon运行时支持不限数量的chrome化的APK,你可以反复进行该操作,你想做多少次都行。Chrome APK subreddit用于跟踪成功/失败情况,所以假如你感到很有用,一定要贴出你的结果。谢谢阅读,希望能帮到大家,请继续关注脚本之家,我们会努力分享更多优秀的文章。