如果说 MyBatis 帮你把数据存进了数据库,那事务就是保证这些数据“要么全对,要么全都不算”的安全锁。
我们还是用最经典的“转账”例子,结合 Spring 的用法把它讲透。
1. 什么是事务?(生活类比)
场景: 你要把 100 块钱转给你的朋友。
这在代码里其实是两步操作:
- 第一步:你的账户余额 减去 100 (
update user set money = money - 100 where id = 你). - 第二步:朋友的账户余额 加上 100 (
update user set money = money + 100 where id = 朋友).
没有事务时(灾难发生):
如果程序执行完第一步,你的钱扣了。突然!断电了、报错了、或者数据库崩了。
- 结果:第二步没执行。你的钱没了,朋友也没收到。这 100 块凭空消失了!这在金融系统里是严重的事故。
有了事务后(安全保障):
事务把这两步操作看作一个“整体” (Atomic Unit)。
- 只要其中任何一步出了错,系统会执行 回滚 (Rollback) 操作。
- 结果:你的账户余额恢复原样,就像这次转账从来没发生过一样。
2. 事务的四大特性 (ACID) —— 面试必背!
为了保证数据的安全,数据库事务必须遵守这四个铁律:
| 特性 | 英文 | 解释 | 比喻 |
|---|---|---|---|
| 原子性 | Atomicity | 要么全做,要么全不做。所有操作是一个不可分割的整体。 | 就像核按钮,按下去就不可撤销,要么爆炸,要么没按。 |
| 一致性 | Consistency | 事务前后,数据总量守恒。 | 能量守恒定律。转账前后,你俩的钱加起来总数是不变的。 |
| 隔离性 | Isolation | 多个用户并发操作时,互不干扰。 | 我在改卷子的时候,你不能偷看。等我改完了(提交了),你才能看。 |
| 持久性 | Durability | 一旦事务提交,数据就永久保存了。哪怕下一秒服务器爆炸,数据也在硬盘里。 | 落笔生根,写在石头上。 |
3. Spring 事务管理:@Transactional
在以前写 JDBC 代码时,开启事务非常麻烦,要写 conn.setAutoCommit(false),还要写一堆 try-catch-rollback。
但在 Spring Boot 中,你只需要一个注解就搞定了: @Transactional。
代码实战:
通常我们把这个注解加在 Service 层 的方法上(因为 Service 层负责业务逻辑,比如转账逻辑)。
Java
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
// 核心注解:告诉 Spring,这个方法是一个事务!
@Transactional
public void transfer(int fromId, int toId, int money) {
// 1. 扣钱
accountMapper.decrease(fromId, money);
// 模拟一个异常:比如这里突然除以 0 报错了
int i = 1 / 0;
// 2. 加钱
accountMapper.increase(toId, money);
}
}
发生了什么?
- Spring 看到
@Transactional,会在方法运行前自动开启事务。 - 执行扣钱。
- 遇到异常 (1/0) 。
- Spring 捕获到异常,自动触发 Rollback(回滚) 。
- 结果:第 2 步没执行,第 1 步被撤销。数据库里的钱一分没少。
4. 一个极其重要的“坑”:rollbackFor
Spring 的 @Transactional 默认是“挑食”的:
- 它默认只在遇到
RuntimeException(运行时异常,如空指针、除零异常)时才回滚。 - 如果你抛出的是
Exception(编译时异常,如文件读写错误),它默认不回滚!
企业开发标准写法:
为了保险起见,我们通常会配置它“遇到任何异常都回滚”:
Java
// 完美写法:不管发生什么异常,都给我回滚!
@Transactional(rollbackFor = Exception.class)
public void transfer(...) { ... }
总结
- 事务:就是后悔药。出错了能让你回到原点。
- ACID:原子性、一致性、隔离性、持久性。
- 怎么用:在 Service 方法上加
@Transactional(rollbackFor = Exception.class)。