# Spring事务

作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
相关源码参考: Spring源码中文注释仓库5.3.x分支 (opens new window)


Spring 事务的本质其实就是数据库对事务的支持, 没有数据库的事务支持, Spring 是无法提供事务功能的。

# 1. 事务基本概念

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

特点

  1. 原子性

    一个事务是一个不可分割的工作单位, 事务中包括的诸操作要么都做, 要么都不做。

  2. 一致性

    事务必须是使数据库从一个一致性状态变到另一个一致性状态。

  3. 隔离性

    一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  4. 持久性

    一个事务一旦提交, 它对数据库中数据的改变就应该是永久性的。

# 2. 事务的基本原理

对于JDBC操作数据库可以按如下步骤操作

  1. 获取连接

    Connection con = DriverManager.get Connection()

  2. 开启事务

    con.setAutoCommit(true/false);

  3. 执行业务代码CRUD

  4. 提交事务/回滚事务

    con.commit() / con.rollback()

  5. 关闭连接

    conn.close()

在使用 Spring 事务管理后, 2和4步骤由spring完成, 以注解使用方式为例, 使用 @Transactional 标记对应的类或者方法。那么怎么实现2 4操作得了? 结合上节 AOP 的思想, 很容易理解, 在 Spring 启动时会扫描这些被标记的类或者方法为其生成代理类, 其中开启事务以及事务回滚都是由代理类完成的, 代理类也只是基于数据库的事务进行操作, 本质上是由数据库通过binlog或者redo logo实现的。

# 3. Spring事务的传播特性

事务的传播特性就是在有多个数据库事务时, Spring如何处理这些事务, 其中前三个较为常用

属性名(Propagation) 作用说明
REQUIRED(默认) 如果当前存在事务,则加入;如果没有,则新建一个事务。
REQUIRES_NEW 总是新建一个事务,如果存在事务,暂停当前事务。
NESTED 如果当前存在事务,则创建嵌套事务撤销点, 在回滚时可以局部回滚,不影响外层事务整体。否则行为类似 REQUIRED
SUPPORTS 如果存在事务则加入事务;如果没有事务,则以非事务方式执行。
NOT_SUPPORTED 总是以非事务方式执行,如果当前有事务,则挂起当前事务。
NEVER 必须在没有事务的情况下执行;如果当前有事务则抛出异常。
MANDATORY 必须存在事务,否则抛出异常。

NESTED需要注意的事, 可以部分回滚, 有嵌套事务时可以只回滚子事务, 而主事务依旧执行

以ServiceA.methodA 调用 ServiceB.methodB 为例

如果A B都是REQUIRED, 当执行到mb时, mB会使用和mA的事务

如果A是REQUIRED, B是REQUIRES_NEW, mB会新建一个事务, 并暂停mA的事务

如果A是REQUIRED, B C都是NESTED, 如下代码

@Transactional(propagation = Propagation.REQUIRED)
public void A() {
    try {
        B();  // B 是 NESTED
    } catch (Exception e) {
        C();  // C 是 NESTED
    }
}
// B异常会执行C, 而不会使外层事务A回滚, BC都是在A的事务中加了各自的撤销点用于局部回滚
1
2
3
4
5
6
7
8
9

# 4. 数据库的隔离级别

数据库事务的隔离级别(Isolation Level)用于控制多个事务并发执行时对数据库数据可见性的控制程度。

  1. READ UNCOMMITTED(读未提交)

    • 定义:一个事务可以读取另一个事务尚未提交的数据。

    • 问题

      • 可能发生脏读:读取到另一个事务尚未提交的数据,如果那个事务回滚了,就读取到了无效数据。
    • 并发性:最高(因为几乎不加锁)

    • 一致性:最弱

  2. READ COMMITTED(读已提交)

    • 定义:一个事务只能读取另一个事务已经提交的数据。

    • 能防止的问题:防止脏读

    • 可能的问题

      • 可能发生不可重复读:同一个事务中多次读取同一条数据,结果可能不一致(因为其他事务可能已经修改并提交)。
    • 数据库支持:这是大多数数据库(如 Oracle、SQL Server)的默认隔离级别。

  3. REPEATABLE READ(可重复读)

    • 定义:在同一个事务中,多次读取同一条记录,读取结果始终一致(除非自己修改)。

    • 能防止的问题

      • 防止脏读不可重复读
    • 可能的问题

      • 可能发生幻读:事务中两次相同条件的查询,结果集不一样,因为其他事务可能插入了新数据。
    • 数据库支持:MySQL InnoDB 的默认隔离级别就是 REPEATABLE READ。

  4. SERIALIZABLE(可串行化)

    • 定义:强制事务串行执行,相当于在每条数据上加锁,保证最高的一致性。

    • 能防止的问题

      • 防止脏读不可重复读幻读
    • 缺点

      • 并发性能最差,容易产生大量锁等待和死锁。
    • ** 使用场景**:对一致性要求极高的关键场景,如银行转账、库存扣减等高价值数据操作。

# 5. Spring 中的隔离级别

常量(Isolation 对应数据库隔离级别 说明描述 可防止的问题
Isolation.DEFAULT 使用数据库默认隔离级别 Spring 不做额外控制,采用底层数据库的默认设置(如 MySQL 是 REPEATABLE READ) 取决于底层数据库
Isolation.READ_UNCOMMITTED 读未提交 可以读取未提交数据,存在脏读
Isolation.READ_COMMITTED 读已提交 只能读取已提交数据,防止脏读 ✔ 脏读
Isolation.REPEATABLE_READ 可重复读 防止不可重复读,同一事务多次读相同数据结果一致 ✔ 脏读、✔ 不可重复读
Isolation.SERIALIZABLE 串行化 最严格,事务串行执行,完全避免并发问题 ✔ 脏读、✔ 不可重复读、✔ 幻读

# 6. Spring 事务 API总览

JdbcTemplate - 这是经典的也是最常用的 Spring 对于JDBC 访问的方案。这也是最低级别的封装,其他的工作模式事实上在底层使用了 JdbcTemplate 作为其底层的实现基础。