国庆节10月1日
--


给新增员工的两张表操作加上事务——从「一组操作要么同时成功、要么同时失败」的概念,到 MySQL 的三步事务控制(start transaction / commit / rollback)与默认自动提交,再到 Spring 一句注解 @Transactional 和它的 debug 日志,最后是事务的 ACID 四大特性
.webp)
69 篇的结尾留下了一个坑:新增员工要往 emp 和 emp_expr 两张表里写数据,如果保存基本信息成功、保存工作经历失败,数据库里就留下一个”缺胳膊少腿”的员工——数据的不完整、不一致。这一篇(PPT 第 12~22 页 + 第 27~28 页)就是来解决它的:事务管理。
PPT 第 12 页是章节页(新增员工 / 事务管理 / 文件上传),第 13 页把”02 事务管理”这一节分成了三块:
介绍&操作 → Spring事务管理 → 四大特性
本篇就按这三块走;71 篇再往上走一层,讲事务的进阶用法(控制”什么样的异常才回滚”、事务的传播行为,以及”无论成功失败都要记录操作日志”的案例)。
PPT 第 14 页给出的定义:
事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
拿”新增员工”举例,DAO 层实际要执行的是两句 SQL:
1-- 1. 保存员工基本信息2insert into emp values (39, 'Tom', '123456', '汤姆', 1, '13300001111', 1, 4000, '1.jpg', '2023-11-01', 1, now(), now());3-- 2. 保存员工的工作经历信息4insert into emp_expr(emp_id, begin, end, company, job) values (39,'2019-01-01', '2020-01-01', '百度', '开发'),5 (39,'2020-01-10', '2022-02-01', '阿里', '架构');在业务上这两句是一件事(“新增一个员工”)的两个步骤,可数据库看到的却是两条彼此无关的语句。
PPT 在这一页底下特别加了一条注意:
默认 MySQL 的事务是自动提交的,也就是说,当执行一条 DML 语句,MySQL 会立即隐式的提交事务。
后果就是上面那两句各自成为一个”独立的小事务”:第一句执行完就已经永久落库了;第二句再失败,也挽不回第一句——69 篇最后那个”员工有了、经历没有”的场景就是这么来的。
PPT 第 15 页给出手工控制事务的写法——事务控制主要三步操作:开启事务、提交事务 / 回滚事务:
1-- 开启事务2start transaction; -- 或者 begin;3-- 1. 保存员工基本信息4insert into emp values (39, 'Tom', '123456', '汤姆', 1, '13300001111', 1, 4000, '1.jpg', '2023-11-01', 1, now(), now());5-- 2. 保存员工的工作经历信息6insert into emp_expr(emp_id, begin, end, company, job) values (39,'2019-01-01', '2020-01-01', '百度', '开发'),7 (39,'2020-01-10', '2022-02-01', '阿里', '架构');8-- 提交事务(全部成功) / 回滚事务(有一个失败)9commit; -- 或者 rollback;| 步骤 | 语句 | 什么时候用 |
|---|---|---|
| 开启事务 | start transaction; 或 begin; | 在整组操作的最前面,告诉数据库”接下来这几条是一件事” |
| 提交事务 | commit; | 所有操作都成功了,把结果永久生效 |
| 回滚事务 | rollback; | 只要有一项失败,把这一组操作全部撤销,回到开启事务前的状态 |
对比一下就清楚了:默认(自动提交)时每条 DML 自己提交自己;写 start transaction; 之后就变成”由你说了算”——中间的 DML 都只是”待定”,直到你 commit 或 rollback。
| PPT 的问题 | 答案 |
|---|---|
| 什么是事务? | 事务是一组操作的集合,是一个不可分割的工作单位。这组操作要么全部成功,要么全部失败 |
| 如何控制事务? | 开启事务:start transaction / begin;;提交事务:commit;(全部成功);回滚事务:rollback;(只要有一项失败) |
| 事务在什么场景用? | 银行转账(A 扣钱、B 加钱必须同时成功)、下单扣减库存(生成订单和扣减库存必须同时成功) |
PPT 第 17~19 页把同一段业务层代码连放三遍,用动画演示思路怎么一步步变。
1@Autowired2private EmpMapper empMapper;3@Autowired4private EmpExprMapper empExprMapper;5
6@Override7public void save(Emp emp) {8 //1.保存员工基本信息9 emp.setCreateTime(LocalDateTime.now());10 emp.setUpdateTime(LocalDateTime.now());11 empMapper.insert(emp);12
13 int i = 1/0; // ← PPT 在这里人为制造一个异常(算数异常)14
15 //2. 保存员工的工作经历信息 - 批量16 Integer empId = emp.getId();17 List<EmpExpr> exprList = emp.getExprList();18 if(!CollectionUtils.isEmpty(exprList)){19 exprList.forEach(empExpr -> empExpr.setEmpId(empId));20 empExprMapper.insertBatch(exprList);21 }22}
save 方法——第一句 insert 已经把员工写进库里了,紧接着的 int i = 1/0; 会抛算数异常,后面保存工作经历的两行根本执行不到PPT 在那张图下面写着结论,正是69 篇结尾那个问题:
保存员工信息成功了,保存工作经历信息失败了,就会造成数据库数据的不完整、不一致。
第 18 页在本页上叠了一层动画,图上出现三个词:
1事务 → 开启事务 → 提交 / 回滚事务意思就是:这两次插入要放进一个事务里——“开启事务”包住整段逻辑,全成功就”提交”,中途出错就”回滚”,让两条 SQL 同生共死。
(这就是69 篇里”无事务”对照实验的反面:本机实测把 @Transactional 去掉后,emp 里有员工、emp_expr 里 0 条——数据就是不一致的。)
第 19 页又叠了一层动画,出现”Spring事务管理”——如果在 Java 代码里真的去写 start transaction / commit / rollback(比如用 JDBC 的 Connection 对象手动开关),业务代码会被事务样板代码淹没。Spring 提供了声明式的做法:一句话注解就够。
@Transactional(PPT 第 21 页)#PPT 第 21 页给出的注解说明:
注解:
@Transactional作用:将当前方法交给 spring 进行事务管理——方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务。 位置:业务(service)层的方法上、类上、接口上。
1@Transactional2@Override3public void save(Emp emp) {4 //1.保存员工基本信息5 emp.setCreateTime(LocalDateTime.now());6 emp.setUpdateTime(LocalDateTime.now());7 empMapper.insert(emp);8
9 //2. 保存员工的工作经历信息10 //... 省略11}三个位置的区别,以及 PPT 为什么标”推荐”:
| 加在哪 | 效果 | 说明 |
|---|---|---|
| 方法上(推荐) | 只给这一个方法加事务 | 一个 Service 方法就是一个业务操作,边界最清楚——课程最终代码就是这么加的 |
| 类上 | 这个类里所有方法都加事务 | 整个 EmpServiceImpl 的方法都会进事务,包括只想读一读的查询 |
| 接口上 | 实现这个接口的类,所有方法都加 | 和类上类似,但影响面更”隐蔽” |
@Transactional 为什么推荐加在业务层方法上?因为”事务边界”就是”业务操作的边界”:Service 的一个方法 = 一件完整的事(37~39 篇的分工——Controller 只管接收和响应、Mapper 只管单条 SQL)。把事务加在 Mapper 上就变成了”每条 SQL 各自一个事务”,加在 Controller 上又太粗(一次请求里可能不止一件业务)——都达不到”两次插入同生共死”的目的。
事务是”看不见的”,PPT 顺带给了一段 application.yml 配置——把 Spring 事务管理器的日志级别调成 debug:
1# 配置日志信息,查看spring事务管理的底层日志2logging:3 level:4 org.springframework.jdbc.support.JdbcTransactionManager: debug这条配置本身就是63 篇讲的”配置文件里调日志级别”:JdbcTransactionManager 是 Spring 管 JDBC 事务的组件,把它设成 debug 之后,一次请求里”开事务 → 干活的 SQL → 提交 / 回滚”的全过程都会打在控制台上(下面实测部分就能看到)。
| PPT 的问题 | 答案 |
|---|---|
Spring 事务管理的注解 @Transactional 的作用是什么? | 会在方法运行之前开启事务,运行完毕后根据运行的结果,来提交或回滚事务 |
| 它可以加在什么位置? | 方法上、类上、接口上(推荐加在业务层的方法上) |
把 PPT 第 17 页那行 int i = 1/0; 用起来,做两次对照实验(工程里的 save 上加了 @Transactional,配置也开了事务日志):
本机实测(一):正常一次新增员工
1# 响应2{"code":1,"msg":"success","data":null}3
4# 服务端日志(摘录,连接对象用 … 省略)5DEBUG o.s.jdbc.support.JdbcTransactionManager - Creating new transaction with name6 [com.itheima.service.impl.EmpServiceImpl.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception7DEBUG o.s.jdbc.support.JdbcTransactionManager - Acquired Connection [HikariProxyConnection@…] for JDBC transaction8DEBUG o.s.jdbc.support.JdbcTransactionManager - Switching JDBC Connection [HikariProxyConnection@…] to manual commit9==> Preparing: insert into emp(username, name, gender, phone, ...) values (?, ?, ?, ?, ...)10<== Updates: 111==> Preparing: insert into emp_expr(emp_id, begin, end, company, job) values (?,?,?,?,?) , (?,?,?,?,?)12<== Updates: 213DEBUG o.s.jdbc.support.JdbcTransactionManager - Initiating transaction commit14DEBUG o.s.jdbc.support.JdbcTransactionManager - Committing JDBC transaction on Connection [HikariProxyConnection@…]三行日志把”事务”这件事讲得很直白:
Creating new transaction ... [EmpServiceImpl.save]——@Transactional 生效了,Spring 在进入 save 方法之前就为它创建了一个事务(名字就是”类名.方法名”);Switching ... to manual commit——把连接的”自动提交”关掉了(对应 PPT 第 14 页说的”MySQL 默认自动提交”):后面的 SQL 不再各自提交,而是等这个事务统一提交;Initiating transaction commit / Committing JDBC transaction——方法正常返回,事务提交;emp 和 emp_expr 的数据这才真正落地。落库结果:emp 生成 id = 38(主键返回),emp_expr 2 条 emp_id = 38。
(本机连的是 MySQL 的 tlias 库、用户名 root;password 换成你自己 MySQL 的密码。)
事务定义字符串末尾那段 -java.lang.Exception 是”回滚规则”——本机工程里写的是 @Transactional(rollbackFor = {Exception.class}),71 篇会专门讲它为什么必须写。
本机实测(二):int i = 1/0; 之后整体回滚
在 empMapper.insert(emp); 后面插入 int i = 1 / 0;,重新构建启动后再发一次同样的请求:
1# 响应2HTTP 状态码 500 (服务端抛 ArithmeticException,接口没返回统一 Result)3
4# 服务端日志(摘录)5DEBUG o.s.jdbc.support.JdbcTransactionManager - Creating new transaction with name6 [com.itheima.service.impl.EmpServiceImpl.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception7==> Preparing: insert into emp(username, name, gender, phone, ...) values (?, ?, ?, ?, ...)8<== Updates: 1 ← 员工已经插进去了9DEBUG o.s.jdbc.support.JdbcTransactionManager - Initiating transaction rollback10DEBUG o.s.jdbc.support.JdbcTransactionManager - Rolling back JDBC transaction on Connection [HikariProxyConnection@…]11
12# 查库13emp 表里 testuser02 → 0 条 ← 回滚了14emp_expr 表里对应记录 → 0 条 ← 回滚了关键对照:日志里明明出现过 <== Updates: 1(emp 确实插进去过一次),但异常一抛,Spring 执行 Initiating transaction rollback → Rolling back JDBC transaction,把这位员工连同他的工作经历(根本没来得及插)一起撤掉——要么同时成功,要么同时失败,正是 PPT 第 14 页那句话。
另外注意:emp_log 表当时仍然有 1 条记录(新增员工:Emp(id=39, username=testuser02, ...))——这不是 bug,而是71 篇要讲的”日志必须独立事务”的效果。
PPT 第 27 页又把”介绍&操作 / Spring事务管理 / 四大特性”的目录重复了一遍(表示换到第三块),第 28 页给出事务的四大特性,合称 ACID:
| 特性 | 英文 | 定义(PPT 原文) |
|---|---|---|
| 原子性 | Atomicity | 事务是不可分割的最小单元,要么全部成功,要么全部失败 |
| 一致性 | Consistency | 事务完成时,必须使所有的数据都保持一致状态 |
| 隔离性 | Isolation | 数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行 |
| 持久性 | Durability | 事务一旦提交或回滚,它对数据库中的数据的改变就是永久的 |
拿”新增员工”把这四条对一遍:
emp 的插入和 emp_expr 的插入绑在一起,1/0 一抛两个都没了(本机实测就是这个效果);commit 之后(或是回滚之后),数据库的状态就定下来了,不会因为后来程序退出、断电(正常情况下)而变回去。| 问题 | 答案 |
|---|---|
| 什么是事务? | 一组操作的集合,一个不可分割的工作单位——要么同时成功,要么同时失败 |
| 为什么需要它? | 像”新增员工”这种一个业务写两张表的事,中间失败会留下数据不完整、不一致 |
| MySQL 事务要注意什么? | 默认自动提交:执行一条 DML 就隐式提交一次,所以两句 SQL 是两个独立事务 |
| SQL 里怎么控制事务? | 三步:start transaction;/begin; 开启 → commit; 提交(全部成功)/ rollback; 回滚(有一项失败) |
| Spring 里怎么控制事务? | 在业务层方法上加 @Transactional:方法前开启事务、成功提交、出异常回滚 |
@Transactional 加在哪? | 方法上、类上、接口上——推荐方法上(一个 Service 方法 = 一个业务操作,边界最清楚) |
| 怎么看见事务在跑? | application.yml 里把 org.springframework.jdbc.support.JdbcTransactionManager 的日志级别设成 debug |
| 本机实测的两次结果? | 正常:Creating new transaction… → Initiating transaction commit,emp id=38 / emp_expr 2 条;1/0:HTTP 500 → Initiating transaction rollback / Rolling back JDBC transaction,emp 与 emp_expr 都是 0 条 |
| 四大特性是哪些? | 原子性(不可分割、要么全成功要么全失败)、一致性(数据保持一致状态)、隔离性(不受外部并发影响)、持久性(提交/回滚后改变是永久的) |
emp 和 emp_expr 两张表写入,中间失败会留下”有员工、没经历”的数据,造成数据的不完整/不一致start transaction;(或 begin;)开启事务 → commit; 提交(全部成功)→ rollback; 回滚(只要有一项失败)@Transactional——方法执行前开启事务,成功执行完提交事务,出现异常回滚事务@Transactional 的位置:方法上、类上、接口上;推荐加在业务(Service)层的方法上(一个方法 = 一个业务操作,边界最清晰)logging.level.org.springframework.jdbc.support.JdbcTransactionManager: debug——配好后能在控制台看到事务的创建、提交/回滚{"code":1,"msg":"success","data":null};日志里有 Creating new transaction with name [com.itheima.service.impl.EmpServiceImpl.save]、Switching JDBC Connection … to manual commit、Initiating transaction commitinsert 后加 int i = 1/0; → HTTP 500,日志 Initiating transaction rollback + Rolling back JDBC transaction,再查库 emp、emp_expr 都是 0 条 2-1 用 SQL 手动控制一次”新增员工”的事务
需求:在客户端里模拟”新增员工”这件事——先插一条员工基本信息,再插这个员工的两条工作经历。要求这两步作为一个整体:两步都执行成功才让数据真正生效;请你故意让第二步失败(比如把表名写错、或者中途执行一条报错的语句),观察第一步的数据还在不在,再用合适的方式把这次操作撤销掉。
(练习文件 test_70_MySQL事务.sql 的题目2-1 里给了写作区。)
一级 · 思路:在整组操作前面加”开启事务”,结尾根据成败挑”提交”或”回滚”;对照实验是”开事务”与”不开事务”各来一次
二级 · 方法:start transaction;(或 begin;);成功用 commit;、失败用 rollback;;不顺手的表可以直接 select * from emp where username = '...'; 验证
三级 · 骨架:
1-- 开启事务2____;3insert into emp(username, name, ...) values ('事务练习', '事务练习', ...);4-- (故意报错的一句,比如表名写错)5-- 提交 / 回滚6____;1-- ===== 实验一:不开事务(默认自动提交)=====2insert into emp(username, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time)3values ('tx-test1', '事务测试一', 1, '13100000001', 1, 5000, '1.jpg', '2024-01-01', 1, now(), now());4-- 假设紧接着的"插工作经历"失败(表名故意写错)5insert into emp_expr_wrong(emp_id, begin, end, company, job) values (...); -- 报错:表不存在6-- 结果:第一条已经落库了(自动提交),数据不一致7
8-- ===== 实验二:开启事务 =====9start transaction; -- 也可以写 begin;10insert into emp(username, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time)11values ('tx-test2', '事务测试二', 1, '13100000002', 1, 5000, '1.jpg', '2024-01-01', 1, now(), now());12insert into emp_expr(emp_id, begin, end, company, job)13values (last_insert_id(),'2019-01-01','2020-01-01','百度','开发'); -- 也可以先查出 id 再写死14commit; -- 两条都成功 → 提交;只要有一项失败就写 rollback;自查:① 第一个实验里,报错之后 select * from emp where username = 'tx-test1'; 能查到——自动提交把第一条永久写进去了;② 第二个实验里,如果中途 rollback;,两条 SQL 的效果全部消失(哪怕第一条已经执行过,日志显示过 1 row affected);③ 想清理实验数据用 delete from emp where username in ('tx-test1','tx-test2');(emp_expr 里对应的记录也一并删掉)。
2-2 让”保存员工”这件事由 Spring 统一管事务
需求:业务层的 save 方法里要执行”保存基本信息”和”批量保存工作经历”两次数据库操作。要求:不用在代码里手写任何开启/提交/回滚语句,只要在方法上加一个东西,就能让这两次操作成为一个事务——方法顺利执行完就提交,中途抛异常就整体回滚。另外,为了能看到事务到底有没有在工作,请打开相关的底层日志(日志级别配在配置文件里)。
(练习文件 test_70_事务管理.java 的题目2-2 里给了写作区。)
一级 · 思路:Spring 提供了”声明式事务”——一个注解贴在业务层方法上;日志则是把 Spring 事务管理器的级别调成 debug
二级 · 方法:方法上加 @Transactional(来自 org.springframework.transaction.annotation);yml 里写 logging.level.org.springframework.jdbc.support.JdbcTransactionManager: debug
三级 · 骨架:@____ public void save(Emp emp) { … };yml 里 logging: → level: → org.springframework.jdbc.support.____: debug
1@Transactional // 方法执行前开启事务,成功提交,异常回滚2@Override3public void save(Emp emp) {4 //1. 保存员工基本信息5 emp.setCreateTime(LocalDateTime.now());6 emp.setUpdateTime(LocalDateTime.now());7 empMapper.insert(emp);8
9 //2. 保存员工的工作经历信息 - 批量10 List<EmpExpr> exprList = emp.getExprList();11 if(!CollectionUtils.isEmpty(exprList)){12 exprList.forEach(empExpr -> empExpr.setEmpId(emp.getId()));13 empExprMapper.insertBatch(exprList);14 }15}1# 配置日志信息,查看spring事务管理的底层日志2logging:3 level:4 org.springframework.jdbc.support.JdbcTransactionManager: debug自查:① 注解是 org.springframework.transaction.annotation.Transactional,别导错包;② 只加注解、不改代码——start transaction/commit/rollback 一句都不用写,Spring 用代理在方法前后替你做了;③ 配好日志后,一次成功请求里能依次看到 Creating new transaction … [EmpServiceImpl.save]、Switching JDBC Connection … to manual commit、Initiating transaction commit(本机实测)。
2-3 把”为什么要事务”和四大特性讲清楚
需求:有人问你——① 为什么”保存员工信息成功了、保存工作经历失败了”这件事不能忍?② MySQL 不是有事务吗,为什么代码里还会出现这种不一致?③ 事务的四大特性分别是什么、各自解决了什么问题?请写一段话(可以配例子)回答清楚,并用”新增员工”这个案例把四条特性各举一次。
(练习文件 test_70_事务管理.java 的题目2-3 里给了写作区。)
一级 · 思路:从”业务上这是一件事、数据库眼里是两条语句”的落差说起;四大特性按 原子性 → 一致性 → 隔离性 → 持久性 的顺序各配一句案例
二级 · 方法:关键词——不可分割的工作单位、要么同时成功要么同时失败、数据的不完整/不一致、默认自动提交、commit/rollback;四大特性的英文名是 Atomicity / Consistency / Isolation / Durability
三级 · 骨架:① 因为”新增员工”是____,两次写入必须____;② MySQL 默认____,两条 DML 是____,所以需要显式地____;③ 原子性=、一致性=、隔离性=、持久性=
① “新增员工”在业务上是一个操作:员工和他的工作经历必须一起存在。基本信息成功、经历失败,数据库就留下一个没有工作经历的残缺员工——数据的完整性(该有的没有)和一致性(两张表对不上)都被破坏,用户看到的结果和实际发生的事不一致,所以不能忍。
② MySQL 默认是自动提交的(执行一条 DML 就立即隐式提交),所以在没有显式开启事务时,两句 insert 是两个互相独立的小事务:第一句一提交就永久生效,第二句失败也撤不回第一句。要避免它,就得把这两句”包成一个事务”——SQL 层用 start transaction + commit/rollback,Spring 里用 @Transactional。
③ 四大特性(ACID):
1/0 抛出后 emp 和 emp_expr 的插入都没了(本机实测 0 条);commit 之后(或回滚之后)重启数据库,emp 里的 id=38 这条还在。 3-1 给”新增员工”加上事务管理,并用 1/0 做一次回滚实证
这一题把本节从头到脚走一遍,重点在”用日志和数据库把结论证明出来”。
@Transactional(保持两次插入不动的逻辑),配置文件里打开事务管理的 debug 日志;emp 有新记录、emp_expr 两条的 emp_id 都等于新员工 id;int i = 1 / 0;),重新构建启动,再发一次请求(换个用户名);emp 和 emp_expr 这两条数据都不在了;(练习文件 test_70_事务管理.java 的”综合题”一段里按这 7 步给了写作区。)
回答:① 为什么说这次回滚证明了”原子性”?② 如果把 @Transactional 去掉再做第 5 步,数据库里会留下什么?(先猜,再动手验证)
涉及知识点
| 知识点 | 在这里的应用 |
|---|---|
| 事务概念与自动提交(PPT 14) | 第 5、6 步——不开事务时两句 insert 各自提交 |
| SQL 三步控制(PPT 15~16) | 第 5、6 步——start transaction/commit/rollback 与 Spring 的对应关系 |
@Transactional(PPT 21~22) | 第 1、3 步——加注解 + 从日志里看创建/提交事务 |
| 事务日志配置(PPT 21) | 第 3、6 步——JdbcTransactionManager 的 debug 日志 |
| ACID 四大特性(PPT 28) | 第 7 步——原子性/一致性/隔离性/持久性各对上一个现象 |
| 数据不一致问题(PPT 17) | 第 5、6 步——1/0 是课堂上专门用来制造失败的开关 |
一级 · 思路:先看清楚”正常一次”的日志长什么样,再用 1/0 把”提交”换成”回滚”,两段日志一对比,结论就出来了
二级 · 方法:@Transactional 贴在 save 上;yml 里把 org.springframework.jdbc.support.JdbcTransactionManager 设为 debug;查库用 select * from emp where username = '...' 与 select * from emp_expr where emp_id = ...
三级 · 骨架:① @____ @Override public void save(Emp emp){ … };② 请求 POST http://localhost:8080/____;③ 三行日志的关键词:Creating new transaction / to manual commit / Initiating transaction ____;⑤ 异常那行:int i = ____;;⑥ 回滚两行:Initiating transaction ____ 与 ____ back JDBC transaction
{"code":1,"msg":"success","data":null}。1DEBUG o.s.jdbc.support.JdbcTransactionManager - Creating new transaction with name2 [com.itheima.service.impl.EmpServiceImpl.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception3DEBUG o.s.jdbc.support.JdbcTransactionManager - Switching JDBC Connection [HikariProxyConnection@…] to manual commit4DEBUG o.s.jdbc.support.JdbcTransactionManager - Initiating transaction commitemp 新记录 id = 38,emp_expr 2 条,emp_id 都是 38(主键返回 + 批量插入都正常)。empMapper.insert(emp); 之后加 int i = 1 / 0;,重新构建启动,换一个 username 再发一次。1DEBUG o.s.jdbc.support.JdbcTransactionManager - Initiating transaction rollback2DEBUG o.s.jdbc.support.JdbcTransactionManager - Rolling back JDBC transaction on Connection [HikariProxyConnection@…]emp 里那条(testuser02)0 条、emp_expr 里对应记录 0 条——注意日志中明明出现过 <== Updates: 1,最终却没留下数据,这正是”回滚”。@Transactional 后再做第 5 步,会看到数据不一致:emp 表里留下了新员工,emp_expr 表里一条都没有(本机实测过这个反面场景)——因为这时的两句 insert 是两个各自提交的独立事务,第一句早就生效了。如果你喜欢,那么欢迎来到我的世界!
了解更多暂未播放



"所有的相遇都是久别重逢。"—— 未知


