网站Logo 苏叶的belog

Spring事务

wdadwa
3
2026-02-26

一、Spring 事务

1.1 什么是事务?

事务(Transaction)是数据库提供的一种机制,用来保证一组操作:

  • 要么全部成功
  • 要么全部失败
  • 不会出现执行一半的情况

现实世界例子:你去银行转账:A 账户扣 100;B 账户加 100,如果扣成功,加失败怎么办?

  • 钱不能凭空消失

  • 要么都成功

  • 要么都失败

这就叫:事务(Transaction)= 一组操作,要么全部成功,要么全部失败

1.2 数据库层面的事务(ACID)

事务的 4 个特性:

特性含义
A – 原子性要么全成功,要么全失败
C – 一致性事务前后数据必须满足规则
I – 隔离性多个事务互不干扰
D – 持久性提交后数据永久保存

数据库本身(MySQL InnoDB)已经支持事务。

1.3 Spring 事务的核心价值没

有 Spring 时,事务怎么写?

在最原始的 JDBC 中:

Connection conn = dataSource.getConnection();
try {
    conn.setAutoCommit(false);

    // 执行SQL1
    // 执行SQL2

    conn.commit();
} catch (Exception e) {
    conn.rollback();
} finally {
    conn.close();
}

问题:

  • 模板代码太多
  • 容易忘记回滚
  • 事务和业务代码强耦合
  • 不利于维护

Spring 做了什么?

  • 把事务管理从业务代码中剥离
  • 用 AOP 自动帮我们做 begin / commit / rollback
  • 支持声明式事务和编程式事务

二、Spring 事务的两种使用方式

Spring 事务 = 两种方式

类型使用方式本质
声明式事务@TransactionalAOP
编程式事务TransactionTemplate手动控制

三,声明式事务

3.1 用法示例

核心注解:

@Transactional

示例:

@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        // 插入订单
        // 扣库存
    }
}

执行流程:

  1. Spring 创建代理对象(JDK / CGLIB)
  2. 调用方法时进入代理
  3. 代理开启事务
  4. 执行方法
  5. 判断异常
  6. 提交或回滚

3.2 @Transactional 全部参数详解

@Transactional(
    propagation = Propagation.REQUIRED,
    isolation = Isolation.DEFAULT,
    readOnly = false,
    timeout = -1,
    rollbackFor = Exception.class,
    noRollbackFor = RuntimeException.class
)

3.2.1 传播行为(Propagation)

事务传播 = 当前方法调用时如何处理已有事务

Spring 定义在:Spring Framework

Propagation 一共有 7 种。


REQUIRED(默认):有事务就加入,没有就创建

示例代码

@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
    orderRepository.save(new Order("order1"));
    logService.saveLogRequired();
    throw new RuntimeException("出错了");
}

LogService:

@Transactional(propagation = Propagation.REQUIRED)
public void saveLogRequired() {
    logRepository.save(new Log("log1"));
}

执行流程

  1. 调用 createOrder()
  2. 创建事务 T1
  3. 调用 saveLogRequired()
  4. 发现已有事务 → 加入 T1
  5. 抛异常
  6. 整个 T1 回滚

最终结果

数据
orders无数据
logs无数据

事务图

T1
 ├── order
 └── log

使用场景

  • 一个完整业务流程
  • 子方法必须和主流程保持一致

REQUIRES_NEW:永远开启新事务,外层事务挂起。

示例代码

@Transactional
public void createOrder() {
    orderRepository.save(new Order("order1"));
    logService.saveLogRequiresNew();
    throw new RuntimeException("出错");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLogRequiresNew() {
    logRepository.save(new Log("log1"));
}

执行流程

  1. 创建事务 T1
  2. 调用 saveLogRequiresNew()
  3. 挂起 T1
  4. 创建 T2
  5. T2 提交
  6. 恢复 T1
  7. T1 抛异常 → 回滚

最终结果

数据
orders回滚
logs存在

事务结构

T1 (order)   回滚
暂停
   ↓
T2 (log)     提交

原理解释:在 Spring Framework 中

  • 使用 ThreadLocal 保存当前事务
  • REQUIRES_NEW 会 suspend 当前事务
  • 创建新的 Connection
  • 新事务独立提交

使用场景

  • 操作日志
  • 审计记录
  • 消息记录
  • 不希望被主事务影响的操作

SUPPORTS:有事务就加入,没有就非事务执行

@Transactional(propagation = Propagation.SUPPORTS)
public void saveLogSupports() {
    logRepository.save(new Log("log"));
}
  1. 外层有事务,加入外层事务。
  2. 外层没有事务,自动提交(非事务)。

使用场景,查询方法常用:

@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)

MANDATORY:必须在事务内执行,否则报错

@Transactional(propagation = Propagation.MANDATORY)
public void saveLogMandatory() {
    logRepository.save(new Log("log"));
}

如果外层没有事务:

IllegalTransactionStateException

使用场景

  • 强依赖主业务流程
  • 不允许独立执行

NOT_SUPPORTED:不能在事务中执行

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void nonTransactionMethod() {
    logRepository.save(new Log("log"));
}

执行:

  • 外层事务挂起
  • 当前方法非事务执行
  • 自动提交

使用场景

  • 大批量查询
  • 报表统计
  • 避免锁表

NEVER:如果有事务 → 报错

@Transactional(propagation = Propagation.NEVER)
public void test() {}

NESTED :嵌套事务(SavePoint)

注意:

  • 只支持 JDBC
  • 必须是 DataSourceTransactionManager

为什么必须是 DataSourceTransactionManager?

Spring 有多个事务管理器:

事务管理器用途
DataSourceTransactionManagerJDBC
JpaTransactionManagerJPA
HibernateTransactionManagerHibernate
JtaTransactionManager分布式事务

Savepoint 是谁提供的?

答案:java.sql.Connection

JDBC API 中有:

Savepoint savepoint = connection.setSavepoint();
connection.rollback(savepoint);

示例

@Transactional
public void createOrder() {
    orderRepository.save(new Order("order1"));
    try {
        logService.saveLogNested();
    } catch (Exception e) {}
}
@Transactional(propagation = Propagation.NESTED)
public void saveLogNested() {
    logRepository.save(new Log("log"));
    throw new RuntimeException();
}

执行流程

  1. T1 创建
  2. 创建 SavePoint
  3. 子事务异常
  4. 回滚到 SavePoint
  5. T1 继续执行
  6. T1 提交

最终结果

数据
orders存在
logs

事务结构

T1
 ├── order
 └── savepoint
        └── log 

3.2.2 事务隔离级别(Isolation)

隔离级别决定并发时数据可见性。

对应数据库标准:

级别解决问题
READ_UNCOMMITTED脏读
READ_COMMITTED不可重复读
REPEATABLE_READ幻读
SERIALIZABLE完全隔离

不同数据库默认不同:

  • MySQL 默认 REPEATABLE_READ
  • Oracle Corporation 默认 READ_COMMITTED

3.2.3 回滚规则

Spring 默认:只回滚 RuntimeException 和 Error

不会回滚:

throw new Exception();

如果要回滚受检异常:

@Transactional(rollbackFor = Exception.class)

3.2.4 readOnly 属性

@Transactional(readOnly = true)

作用:

  • 提示数据库优化
  • 禁止写操作(某些数据库)

但它不是强制限制,他只是提示优化!

3.2.5 timeout

@Transactional(timeout = 3)

3 秒没执行完 → 回滚。

四,编程式事务

4.1 用法示例

使用:

@Autowired
private TransactionTemplate transactionTemplate;

transactionTemplate.execute(status -> {
    try {
        // 业务逻辑
    } catch (Exception e) {
        status.setRollbackOnly();
    }
    return null;
});

优点:

  • 可以精确控制某一段代码
  • 不受 AOP 限制
  • 适合复杂流程

4.2 为什么需要编程式事务?

声明式事务有一个天然限制:它基于 AOP,只能控制“方法级别”的事务边界。

但现实业务中常见场景:

  • 一部分代码要提交,后面代码失败不影响前面
  • 循环里每条数据单独事务
  • try/catch 里精确控制回滚
  • 动态决定是否开启事务
  • 跨多个逻辑块

这时候:声明式事务就不够用了!

4.3 Spring 提供的两种编程式事务方式

4.3.1 TransactionTemplate

  1. 基础使用

    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void test() {
        transactionTemplate.execute(status -> {
            orderMapper.insert(...);
            return null;
        });
    }
    
  2. 执行流程

    execute()
      ↓
    getTransaction()
      ↓
    执行回调
      ↓
    commit / rollback
    
  3. 回滚控制

    默认:

    • 抛 RuntimeException → 回滚
    • 正常返回 → 提交

    你也可以手动控制:

    transactionTemplate.execute(status -> {
        try {
            orderMapper.insert(...);
        } catch (Exception e) {
            status.setRollbackOnly();
        }
        return null;
    });
    
  4. 设置属性

    transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 传播行为
    
    transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ); // 隔离机制
    transactionTemplate.setTimeout(5); 
    transactionTemplate.setReadOnly(true);
    

它等价于:

@Transactional(...)

但你是手动控制的。

4.3.2 PlatformTransactionManager

这是最原始的事务控制。

@Autowired
private PlatformTransactionManager txManager;
  1. 手动完整写法

    public void test() {
    
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    
        TransactionStatus status = txManager.getTransaction(definition);
    
        try {
            orderMapper.insert(...);
            txManager.commit(status);
        } catch (Exception e) {
            txManager.rollback(status);
        }
    }
    
  2. 这和 JDBC 有什么区别?

    • JDBC

      conn.setAutoCommit(false);
      conn.commit();
      
    • Spring

      txManager.getTransaction()
      txManager.commit()
      

      Spring 帮你:

      • 管理连接
      • 管理线程绑定
      • 管理嵌套事务
      • 管理传播行为

4.4 编程式事务 vs 声明式事务对比

对比声明式编程式
控制粒度方法级代码块级
是否 AOP
是否支持 self 调用
复杂逻辑控制
可读性一般

五,Spring 事务的底层原理

5.1 核心流程

在 Spring Framework 中:Spring 事务 = AOP 代理 + 事务拦截器 + 事务管理器 + ThreadLocal 资源绑定

从调用开始:一次事务到底发生了什么?

假设代码:

@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        orderMapper.insert(...);
    }
}

当调用:

orderService.createOrder();

真正的执行流程是:

调用代理对象
   ↓
TransactionInterceptor
   ↓
获取事务管理器
   ↓
创建事务
   ↓
绑定 Connection 到 ThreadLocal
   ↓
执行目标方法
   ↓
commit / rollback
   ↓
清理 ThreadLocal
  1. 代理是怎么来的?

    Spring 在启动时会解析:

    @Transactional
    

    然后通过 AOP 为 Bean 创建代理对象。

    如果类实现接口 → JDK 动态代理
    否则 → CGLIB 代理

    所以:调用的不是原始对象,而是代理对象。

  2. 进入 TransactionInterceptor

    事务增强的核心类是:TransactionInterceptor

    它实现了 AOP 的 MethodInterceptor

    核心方法:invoke(MethodInvocation invocation)

    执行流程:

    public Object invoke(MethodInvocation invocation) {
    
        // 1. 获取事务属性
        TransactionAttribute txAttr = getTransactionAttribute();
    
        // 2. 获取事务管理器
        PlatformTransactionManager tm = determineTransactionManager();
    
        // 3. 开启事务
        TransactionInfo txInfo = createTransactionIfNecessary();
    
        try {
            // 4. 执行目标方法
            Object retVal = invocation.proceed();
    
            // 5. 提交
            commitTransactionAfterReturning(txInfo);
    
            return retVal;
        }
        catch (Throwable ex) {
    
            // 6. 回滚
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
    }
    

    这就是核心流程。

  3. 创建事务

    真正的创建逻辑在:PlatformTransactionManager

    最常见实现类:DataSourceTransactionManager

    核心方法:getTransaction(TransactionDefinition definition)

    执行流程:

    判断当前线程是否已有事务
       ↓
    根据传播行为决定:
       - 加入
       - 新建
       - 挂起
       ↓
    如果新建:
       doBegin()
    
  4. doBegin() 做了什么?

    在 DataSourceTransactionManager 里:

    protected void doBegin(...) {
    
        Connection con = dataSource.getConnection();
    
        con.setAutoCommit(false);
    
        if (definition.isReadOnly()) {
            con.setReadOnly(true);
        }
    
        // 绑定到线程
        TransactionSynchronizationManager.bindResource(dataSource, connectionHolder);
    }
    
  5. ThreadLocal 资源绑定(核心)

    Spring 用:TransactionSynchronizationManager

    内部有:private static final ThreadLocal<Map<Object, Object>> resources;

    作用:把 Connection 绑定到当前线程。

    这意味着:

    • 同一个线程
    • 同一个事务
    • 同一个数据库连接
  6. 执行目标方法

    当执行:orderMapper.insert(...)

    MyBatis 或 JPA 获取连接时:不会新建连接,而是从:

    TransactionSynchronizationManager
    

    中获取已经绑定的连接。

    所以:同一事务内所有 SQL 使用同一个 Connection

  7. 提交流程

    当方法正常返回:commitTransactionAfterReturning()

    内部调用:txManager.commit(status);

    DataSourceTransactionManager 中:

    protected void doCommit(DefaultTransactionStatus status) {
        Connection con = ...
        con.commit();
    }
    

    然后:

    • 解绑 ThreadLocal
    • 关闭连接
    • 清理同步器
  8. 回滚流程

    如果发生异常:completeTransactionAfterThrowing()

    内部:txManager.rollback(status);

    最终:con.rollback();

完整流程图

@Transaction 方法调用
        ↓
AOP 代理
        ↓
TransactionInterceptor
        ↓
PlatformTransactionManager.getTransaction()
        ↓
doBegin()
        ↓
绑定 Connection 到 ThreadLocal
        ↓
执行 SQL
        ↓
是否异常?
   ↓             ↓
commit         rollback
        ↓
清理 ThreadLocal

5.2 PlatformTransactionManager 体系

核心接口:

PlatformTransactionManager

常见实现:

实现类场景
DataSourceTransactionManager单数据源
JpaTransactionManagerJPA
HibernateTransactionManagerHibernate
JtaTransactionManager分布式事务

六,事务失效的 8 大常见原因

在 Spring Framework 中:Spring 事务本质是 AOP 代理。所以,所有事务失效,本质都围绕一个核心:是否经过代理对象

6.1 方法不是 public

错误示例

@Transactional
private void createOrder() {
    orderMapper.insert(...);
}

为什么失效?

Spring 默认使用 AOP 代理:

  • JDK 动态代理 → 只能代理接口 public 方法
  • CGLIB → 也只拦截 public 方法

private 方法:

  • 不会被代理增强
  • 不会进入 TransactionInterceptor

6.2 self 调用

示例

public void outer() {
    this.inner();   // 事务失效
}

@Transactional
public void inner() {
    orderMapper.insert(...);
}

为什么失效?

调用路径:

this.inner()

没有走代理对象。

代理结构:

代理对象
   ↓
真实对象

self 调用时:

真实对象 → 真实对象

绕过了代理。

解决方案

  1. 注入自己

    @Autowired
    private OrderService self;
    
    self.inner();
    
  2. 使用 AopContext

    ((OrderService) AopContext.currentProxy()).inner();
    

6.3 final 方法

示例

@Transactional
public final void test() {}

为什么失效?

CGLIB 原理:通过继承目标类,重写方法,而 final 方法不能被重写,所以无法增强。

6.4 static 方法

示例

@Transactional
public static void test() {}

原因

AOP 是基于对象代理。

static 方法:

  • 不属于对象
  • 无法代理

6.5 异常被try-catch吃掉

示例

@Transactional
public void test() {
    try {
        orderMapper.insert(...);
        int i = 1 / 0;
    } catch (Exception e) {
        // 什么也没做
    }
}

为什么失效?

Spring 事务判断回滚依据:方法是否抛出异常

如果异常被吞掉:

  • 方法正常返回
  • Spring 执行 commit

解决方案

  1. 手动回滚

    catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    
    
  2. 将异常抛出

6.6 非 Spring 管理对象

示例

OrderService service = new OrderService();
service.createOrder();

为什么失效?

Spring 事务依赖:

Spring 容器管理的 Bean

手动 new 的对象:

  • 没有代理
  • 没有事务增强

正确方式

@Autowired
private OrderService service;

6.7 多线程

示例

@Transactional
public void test() {
    new Thread(() -> {
        orderMapper.insert(...);
    }).start();
}

为什么失效?

Spring 使用:ThreadLocal

  • 绑定事务资源。

  • 事务只存在当前线程。

新线程:

  • 没有绑定 Connection
  • 不在事务内

解决方案

  • 不在事务里开线程
  • 或者使用分布式事务
  • 或者编程式事务控制

6.8 数据库不支持事务(MyISAM)

例如在 MySQL 中:

如果表使用:MyISAM

则:

  • 不支持事务
  • commit / rollback 无效

必须使用:InnoDB

动物装饰