初识事务

22 阅读3分钟

如果说 MyBatis 帮你把数据存进了数据库,那事务就是保证这些数据“要么全对,要么全都不算”的安全锁

我们还是用最经典的“转账”例子,结合 Spring 的用法把它讲透。


1. 什么是事务?(生活类比)

场景: 你要把 100 块钱转给你的朋友。

这在代码里其实是两步操作:

  1. 第一步:你的账户余额 减去 100 (update user set money = money - 100 where id = 你).
  2. 第二步:朋友的账户余额 加上 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);
    }
}

发生了什么?

  1. Spring 看到 @Transactional,会在方法运行前自动开启事务。
  2. 执行扣钱。
  3. 遇到异常 (1/0)
  4. Spring 捕获到异常,自动触发 Rollback(回滚)
  5. 结果:第 2 步没执行,第 1 步被撤销。数据库里的钱一分没少。

4. 一个极其重要的“坑”:rollbackFor

Spring 的 @Transactional 默认是“挑食”的:

  • 它默认只在遇到 RuntimeException(运行时异常,如空指针、除零异常)时才回滚。
  • 如果你抛出的是 Exception(编译时异常,如文件读写错误),它默认不回滚

企业开发标准写法:

为了保险起见,我们通常会配置它“遇到任何异常都回滚”:

Java

// 完美写法:不管发生什么异常,都给我回滚!
@Transactional(rollbackFor = Exception.class)
public void transfer(...) { ... }

总结

  • 事务:就是后悔药。出错了能让你回到原点。
  • ACID:原子性、一致性、隔离性、持久性。
  • 怎么用:在 Service 方法上加 @Transactional(rollbackFor = Exception.class)