内容简介:前面几篇博文介绍了声明式事务本文的case,将使用声明式事务,首先我们创建一个SpringBoot项目,版本为在项目
前面几篇博文介绍了声明式事务 @Transactional
的使用姿势,只知道正确的使用姿势可能还不够,还得知道什么场景下不生效,避免采坑。本文将主要介绍让事务不生效的几种case
I. 配置
本文的case,将使用声明式事务,首先我们创建一个SpringBoot项目,版本为 2.2.1.RELEASE
,使用 mysql 作为目标数据库,存储引擎选择 Innodb
,事务隔离级别为RR
1. 项目配置
在项目 pom.xml
文件中,加上 spring-boot-starter-jdbc
,会注入一个 DataSourceTransactionManager
的bean,提供了事务支持
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> ``` ### 2. 数据库配置 进入spring配置文件`application.properties`,设置一下db相关的信息 ```properties ## DataSource spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false spring.datasource.username=root spring.datasource.password=
3. 数据库
新建一个简单的表结构,用于测试
CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=551 DEFAULT CHARSET=utf8mb4;
II. 不生效case
在声明式事务的使用教程 200119-SpringBoot系列教程之声明式事务Transactional 中,也提到了一些事务不生效的方式,比如声明式事务注解 @Transactional
主要是结合代理实现,结合AOP的知识点,至少可以得出放在私有方法上,类内部调用都不会生效,下面进入详细说明
1. 数据库
事务生效的前提是你的数据源得支持事务,比如mysql的MyISAM引擎就不支持事务,而Innodb支持事务
下面的case都是基于mysql + Innodb 引擎
为后续的演示case,我们准备一些数据如下
@Service public class NotEffectDemo { @Autowired private JdbcTemplate jdbcTemplate; @PostConstruct public void init() { String sql = "replace into money (id, name, money) values" + " (520, '初始化', 200)," + "(530, '初始化', 200)," + "(540, '初始化', 200)," + "(550, '初始化', 200)"; jdbcTemplate.execute(sql); } }
2. 类内部访问
简单来讲就是指非直接访问带注解标记的方法B,而是通过类普通方法A,然后由A访问B
下面是一个简单的case
/** * 非直接调用,不生效 * * @param id * @return * @throws Exception */ @Transactional(rollbackFor = Exception.class) public boolean testCompileException2(int id) throws Exception { if (this.updateName(id)) { this.query("after update name", id); if (this.update(id)) { return true; } } throw new Exception("参数异常"); } public boolean testCall(int id) throws Exception { return testCompileException2(id); }
上面两个方法,直接调用 testCompleException
方法,事务正常操作;通过调用 testCall
间接访问,在不生效
测试case如下:
@Component public class NotEffectSample { @Autowired private NotEffectDemo notEffectDemo; public void testNotEffect() { testCall(530, (id) -> notEffectDemo.testCall(530)); } private void testCall(int id, CallFunc<Integer, Boolean> func) { System.out.println("============ 事务不生效case start ========== "); notEffectDemo.query("transaction before", id); try { // 事务可以正常工作 func.apply(id); } catch (Exception e) { } notEffectDemo.query("transaction end", id); System.out.println("============ 事务不生效case end ========== \n"); } @FunctionalInterface public interface CallFunc<T, R> { R apply(T t) throws Exception; } }
输出结果如下:
============ 事务不生效case start ========== transaction before >>>> {id=530, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} after update name >>>> {id=530, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} transaction end >>>> {id=530, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} ============ 事务不生效case end ==========
从上面的输出可以看到,事务并没有回滚,主要是因为类内部调用,不会通过代理方式访问
3. 私有方法
在私有方法上,添加 @Transactional
注解也不会生效,私有方法外部不能访问,所以只能内部访问,上面的case不生效,这个当然也不生效了
/** * 私有方法上的注解,不生效 * * @param id * @return * @throws Exception */ @Transactional private boolean testSpecialException(int id) throws Exception { if (this.updateName(id)) { this.query("after update name", id); if (this.update(id)) { return true; } } throw new Exception("参数异常"); }
直接使用时,下面这种场景不太容易出现,因为IDEA会有提醒,文案为: Methods annotated with '@Transactional' must be overridable
4. 异常不匹配
@Transactional
注解默认处理运行时异常,即只有抛出运行时异常时,才会触发事务回滚,否则并不会如
/** * 非运行异常,且没有通过 rollbackFor 指定抛出的异常,不生效 * * @param id * @return * @throws Exception */ @Transactional public boolean testCompleException(int id) throws Exception { if (this.updateName(id)) { this.query("after update name", id); if (this.update(id)) { return true; } } throw new Exception("参数异常"); }
测试case如下
public void testNotEffect() { testCall(520, (id) -> notEffectDemo.testCompleException(520)); }
输出结果如下,事务并未回滚(如果需要解决这个问题,通过设置 @Transactional
的rollbackFor属性即可)
============ 事务不生效case start ========== transaction before >>>> {id=520, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} after update name >>>> {id=520, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} transaction end >>>> {id=520, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} ============ 事务不生效case end ==========
5. 多线程
这个场景可能并不多见,在标记事务的方法内部,另起子线程执行db操作,此时事务同样不会生效
下面给出两个不同的姿势,一个是子线程抛异常,主线程ok;一个是子线程ok,主线程抛异常
a. case1
/** * 子线程抛异常,主线程无法捕获,导致事务不生效 * * @param id * @return */ @Transactional(rollbackFor = Exception.class) public boolean testMultThread(int id) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { updateName(id); query("after update name", id); } }).start(); new Thread(new Runnable() { @Override public void run() { boolean ans = update(id); query("after update id", id); if (!ans) { throw new RuntimeException("failed to update ans"); } } }).start(); Thread.sleep(1000); System.out.println("------- 子线程 --------"); return true; }
上面这种场景不生效很好理解,子线程的异常不会被外部的线程捕获, testMultThread
这个方法的调用不抛异常,因此不会触发事务回滚
public void testNotEffect() { testCall(540, (id) -> notEffectDemo.testMultThread(540)); }
输出结果如下
============ 事务不生效case start ========== transaction before >>>> {id=540, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} after update name >>>> {id=540, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} Exception in thread "Thread-3" java.lang.RuntimeException: failed to update ans at com.git.hui.boot.jdbc.demo.NotEffectDemo$2.run(NotEffectDemo.java:112) at java.lang.Thread.run(Thread.java:748) after update id >>>> {id=540, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} ------- 子线程 -------- transaction end >>>> {id=540, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} ============ 事务不生效case end ==========
b. case2
/** * 子线程抛异常,主线程无法捕获,导致事务不生效 * * @param id * @return */ @Transactional(rollbackFor = Exception.class) public boolean testMultThread2(int id) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { updateName(id); query("after update name", id); } }).start(); new Thread(new Runnable() { @Override public void run() { boolean ans = update(id); query("after update id", id); } }).start(); Thread.sleep(1000); System.out.println("------- 子线程 --------"); update(id); query("after outer update id", id); throw new RuntimeException("failed to update ans"); }
上面这个看着好像没有毛病,抛出线程,事务回滚,可惜两个子线程的修改并不会被回滚
测试代码
public void testNotEffect() { testCall(550, (id) -> notEffectDemo.testMultThread2(550)); }
从下面的输出也可以知道,子线程的修改并不在同一个事务内,不会被回滚
============ 事务不生效case start ========== transaction before >>>> {id=550, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:38.0} after update name >>>> {id=550, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:40.0} after update id >>>> {id=550, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:40.0} ------- 子线程 -------- after outer update id >>>> {id=550, name=更新, money=220, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:41.0} transaction end >>>> {id=550, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:40.0} ============ 事务不生效case end ==========
6. 传播属性
上一篇关于传播属性的博文中,介绍了其中有几种是不走事务执行的,所以也需要额外注意下,详情可以参考博文 200202-SpringBoot系列教程之事务传递属性
7. 小结
下面小结几种 @Transactional
注解事务不生效的case
- 数据库不支持事务
- 注解放在了私有方法上
- 类内部调用
- 未捕获异常
- 多线程场景
- 传播属性设置问题
III. 其他
0. 系列博文&源码
系列博文
- 180926-SpringBoot高级篇DB之基本使用
- 190407-SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解
- 190412-SpringBoot高级篇JdbcTemplate之数据查询上篇
- 190417-SpringBoot高级篇JdbcTemplate之数据查询下篇
- 190418-SpringBoot高级篇JdbcTemplate之数据更新与删除
- 200119-SpringBoot系列教程之声明式事务Transactional
- 200120-SpringBoot系列教程之事务隔离级别知识点小结
- 200202-SpringBoot系列教程之事务传递属性
源码
- 工程: https://github.com/liuyueyi/spring-boot-demo
- 实例源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate-transaction
1. 一灰灰Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰Blog个人博客 https://blog.hhui.top
- 一灰灰Blog-Spring专题博客 http://spring.hhui.top
打赏 如果觉得我的文章对您有帮助,请随意打赏。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- @Transactional事务生效问题
- Spring Boot 系列教程之事务不生效的几种 case
- UI抗锯齿不生效原因
- properties文件改变不生效的问题
- View.requestLayout() 不生效的问题
- C# Redis 过期机制不生效问题
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。