Spring 事务管理实战指南

Spring 事务管理实战指南

一、概述

事务是数据库操作的基本单元,保证一组操作要么全部成功,要么全部回滚。Spring 提供了多种事务管理方式,从声明式注解到编程式 API,适用于不同复杂度的场景。

本文以一个电商订单支付场景为例,系统介绍 Spring 事务的实现方式、传播行为、隔离级别及常见陷阱。

注:

博客:

https://blog.csdn.net/badao_liumang_qizhi


二、示例场景

java 复制代码
// 支付成功后:扣减库存 + 更新订单状态 + 记录支付流水
// 三个操作必须在同一事务中,任一失败全部回滚

三、方案一:声明式事务(@Transactional 注解)

3.1 基本用法

java 复制代码
@Service
public class OrderPaymentService {

    @Resource
    private OrderRepository orderRepository;
    @Resource
    private StockRepository stockRepository;
    @Resource
    private PaymentRecordRepository paymentRecordRepository;

    @Transactional(rollbackFor = Exception.class)
    public void processPayment(PaymentDto paymentDto) {
        // 1. 扣减库存
        Stock stock = stockRepository.findBySkuId(paymentDto.getSkuId());
        stock.setQty(stock.getQty() - paymentDto.getQty());
        stockRepository.save(stock);

        // 2. 更新订单状态
        Order order = orderRepository.findByOrderNo(paymentDto.getOrderNo());
        order.setStatus("PAID");
        orderRepository.save(order);

        // 3. 记录支付流水
        PaymentRecord record = new PaymentRecord();
        record.setOrderNo(paymentDto.getOrderNo());
        record.setAmount(paymentDto.getAmount());
        record.setPayTime(new Date());
        paymentRecordRepository.save(record);
    }
}

3.2 注解属性详解

属性 默认值 说明
propagation REQUIRED 事务传播行为
isolation DEFAULT 事务隔离级别
timeout -1(无超时) 事务超时时间(秒)
readOnly false 是否只读事务
rollbackFor RuntimeException 触发回滚的异常类型
noRollbackFor 不触发回滚的异常类型

3.3 rollbackFor 的重要性

java 复制代码
// 错误:checked exception 不会触发回滚
@Transactional
public void process() throws IOException {
    orderRepository.save(order);
    throw new IOException("文件写入失败"); // 事务不会回滚!
}

// 正确:显式指定 rollbackFor
@Transactional(rollbackFor = Exception.class)
public void process() throws IOException {
    orderRepository.save(order);
    throw new IOException("文件写入失败"); // 事务会回滚
}

规则

  • 默认只对 RuntimeExceptionError 回滚
  • checked Exception(如 IOException)默认不回滚
  • 建议始终写 rollbackFor = Exception.class

3.4 实现原理

Spring 通过 AOP 代理实现声明式事务:

复制代码
调用方 -> Spring 代理对象 -> 开启事务 -> 执行目标方法 -> 提交/回滚事务

代理方式:

  • JDK 动态代理(接口)
  • CGLIB 代理(类)

四、事务传播行为(Propagation)

4.1 七种传播行为

传播行为 说明 典型场景
REQUIRED 有事务则加入,无则新建 默认值,大多数业务方法
REQUIRES_NEW 总是新建事务,挂起当前事务 日志记录、审计(独立于主事务)
NESTED 在当前事务中创建保存点(嵌套事务) 部分失败可回滚到保存点
SUPPORTS 有事务则加入,无则非事务执行 查询方法
NOT_SUPPORTED 总是非事务执行,挂起当前事务 不需要事务的操作
MANDATORY 必须在事务中调用,否则抛异常 强制要求调用方有事务
NEVER 必须在非事务中调用,否则抛异常 禁止在事务中执行的操作

4.2 REQUIRED vs REQUIRES_NEW vs NESTED

java 复制代码
@Service
public class OuterService {

    @Resource
    private InnerService innerService;

    @Transactional(rollbackFor = Exception.class)
    public void outerMethod() {
        // 操作 A
        orderRepository.save(order);

        // 调用内部方法
        innerService.innerMethod();

        // 操作 B
        stockRepository.save(stock);
    }
}

@Service
public class InnerService {

    // 场景1:REQUIRED(默认)
    // innerMethod 加入 outerMethod 的事务
    // innerMethod 异常 -> 整个事务回滚(A、B 都回滚)
    @Transactional(propagation = Propagation.REQUIRED)
    public void innerMethod() { ... }

    // 场景2:REQUIRES_NEW
    // innerMethod 在独立事务中执行
    // innerMethod 异常 -> 只回滚 innerMethod,outerMethod 可以 catch 后继续
    // outerMethod 异常 -> 不影响已提交的 innerMethod
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void innerMethod() { ... }

    // 场景3:NESTED
    // innerMethod 在 outerMethod 事务的保存点中执行
    // innerMethod 异常 -> 回滚到保存点,outerMethod 可以 catch 后继续
    // outerMethod 异常 -> innerMethod 也回滚(因为是同一物理事务)
    @Transactional(propagation = Propagation.NESTED)
    public void innerMethod() { ... }
}

4.3 REQUIRES_NEW 的注意事项

java 复制代码
// 注意:REQUIRES_NEW 会挂起外层事务,占用额外数据库连接
// 高并发下可能耗尽连接池

// 外层事务持有连接 A(被挂起)
// 内层事务获取连接 B(新事务)
// 如果连接池只有 N 个连接,并发 N 个请求时可能死锁

五、事务隔离级别(Isolation)

5.1 四种隔离级别

隔离级别 脏读 不可重复读 幻读 说明
READ_UNCOMMITTED 可能 可能 可能 最低隔离,几乎不用
READ_COMMITTED 不会 可能 可能 Oracle 默认
REPEATABLE_READ 不会 不会 可能 MySQL InnoDB 默认
SERIALIZABLE 不会 不会 不会 最高隔离,性能最差

5.2 使用方式

java 复制代码
// 使用数据库默认隔离级别(推荐)
@Transactional(isolation = Isolation.DEFAULT)

// 需要读已提交(避免脏读,允许不可重复读)
@Transactional(isolation = Isolation.READ_COMMITTED)

// 需要可重复读(MySQL 默认)
@Transactional(isolation = Isolation.REPEATABLE_READ)

5.3 实际建议

  • 大多数场景使用数据库默认隔离级别即可
  • MySQL InnoDB 的 REPEATABLE_READ 通过 MVCC 实现,性能开销不大
  • 只有在明确需要更低隔离级别(如报表查询)时才手动指定

六、方案二:编程式事务(TransactionTemplate)

6.1 适用场景

  • 需要在方法内部精细控制事务边界
  • 部分代码需要事务,部分不需要
  • 需要根据条件决定是否提交/回滚

6.2 实现方式

java 复制代码
@Service
public class OrderPaymentService {

    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private OrderRepository orderRepository;
    @Resource
    private PaymentLogRepository paymentLogRepository;

    public Boolean processPayment(PaymentDto paymentDto) {
        // 事务内:核心业务
        Boolean result = transactionTemplate.execute(status -> {
            try {
                Order order = orderRepository.findByOrderNo(paymentDto.getOrderNo());
                order.setStatus("PAID");
                orderRepository.save(order);
                return true;
            } catch (Exception e) {
                status.setRollbackOnly(); // 手动标记回滚
                return false;
            }
        });

        // 事务外:记录日志(不影响主事务)
        try {
            PaymentLog log = new PaymentLog();
            log.setOrderNo(paymentDto.getOrderNo());
            log.setResult(result ? "SUCCESS" : "FAIL");
            paymentLogRepository.save(log);
        } catch (Exception e) {
            // 日志失败不影响返回结果
        }

        return result;
    }
}

6.3 TransactionTemplate 配置

java 复制代码
@Configuration
public class TransactionConfig {

    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager manager) {
        TransactionTemplate template = new TransactionTemplate(manager);
        template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        template.setTimeout(30); // 30秒超时
        return template;
    }
}

6.4 与声明式对比

维度 @Transactional TransactionTemplate
控制粒度 方法级别 代码块级别
灵活性
代码侵入性
可读性 一般
适用场景 整个方法需要事务 方法内部分代码需要事务

七、方案三:编程式事务(PlatformTransactionManager)

7.1 最底层的事务控制

java 复制代码
@Service
public class OrderPaymentService {

    @Resource
    private PlatformTransactionManager transactionManager;
    @Resource
    private OrderRepository orderRepository;

    public void processPayment(PaymentDto paymentDto) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        def.setTimeout(30);

        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            Order order = orderRepository.findByOrderNo(paymentDto.getOrderNo());
            order.setStatus("PAID");
            orderRepository.save(order);

            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

7.2 适用场景

  • 需要完全手动控制事务生命周期
  • 框架集成或底层工具类
  • 一般业务代码不推荐使用

八、方案四:XML 声明式事务(AOP 配置)

8.1 实现方式

xml 复制代码
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/>
    </tx:attributes>
</tx:advice>

<aop:config proxy-target-class="true">
    <aop:pointcut id="txPointcut"
                  expression="execution(* com.example.service..*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

8.2 优缺点

优点 缺点
统一管理,不侵入代码 配置与代码分离,不直观
按方法名模式批量配置 灵活性差,无法针对单个方法定制
适合遗留项目统一加事务 新项目不推荐

8.3 适用场景

  • 遗留项目需要统一添加事务管理
  • 团队规范要求统一事务配置
  • 与注解方式可以共存(注解优先级更高)

九、@Transactional 失效的常见陷阱

9.1 自调用问题

java 复制代码
@Service
public class OrderService {

    // 事务不生效!因为是 this 调用,不经过代理
    public void methodA() {
        this.methodB(); // 直接调用,不走 AOP 代理
    }

    @Transactional(rollbackFor = Exception.class)
    public void methodB() {
        // 期望有事务,但实际没有
    }
}

解决方案

java 复制代码
// 方案1:注入自身(推荐)
@Service
public class OrderService {

    @Resource
    private OrderService self; // 注入代理对象

    public void methodA() {
        self.methodB(); // 通过代理调用,事务生效
    }

    @Transactional(rollbackFor = Exception.class)
    public void methodB() { ... }
}

// 方案2:拆分到不同 Service
@Service
public class OrderService {

    @Resource
    private OrderTransactionService orderTransactionService;

    public void methodA() {
        orderTransactionService.methodB(); // 跨 Bean 调用,事务生效
    }
}

// 方案3:从 ApplicationContext 获取代理
@Service
public class OrderService implements ApplicationContextAware {

    private ApplicationContext context;

    public void methodA() {
        OrderService proxy = context.getBean(OrderService.class);
        proxy.methodB();
    }

    @Transactional(rollbackFor = Exception.class)
    public void methodB() { ... }
}

9.2 异常被吞

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void process() {
    try {
        orderRepository.save(order);
        int x = 1 / 0; // 异常
    } catch (Exception e) {
        log.error("处理失败", e);
        // 异常被 catch 了,Spring 感知不到,事务不会回滚!
    }
}

解决方案

java 复制代码
// 方案1:catch 后重新抛出
catch (Exception e) {
    log.error("处理失败", e);
    throw e; // 重新抛出,事务回滚
}

// 方案2:手动标记回滚
catch (Exception e) {
    log.error("处理失败", e);
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

9.3 方法访问修饰符

java 复制代码
// 事务不生效!@Transactional 只对 public 方法有效(基于代理)
@Transactional(rollbackFor = Exception.class)
private void process() { ... }  // private 方法,代理无法拦截

@Transactional(rollbackFor = Exception.class)
protected void process() { ... }  // protected,CGLIB 代理可能生效,但不推荐

规则@Transactional 只应用于 public 方法。

9.4 非 Spring 管理的类

java 复制代码
// 事务不生效!类没有被 Spring 管理
public class OrderService { // 缺少 @Service 或 @Component
    @Transactional(rollbackFor = Exception.class)
    public void process() { ... }
}

9.5 多数据源未指定事务管理器

java 复制代码
// 如果有多个数据源,需要指定使用哪个事务管理器
@Transactional(value = "secondaryTransactionManager", rollbackFor = Exception.class)
public void process() { ... }

十、只读事务的优化

java 复制代码
// 查询方法标记为只读事务
@Transactional(readOnly = true)
public List<Order> findOrders(String userId) {
    return orderRepository.findByUserId(userId);
}

好处

  • MySQL 对只读事务有优化(不记录 undo log)
  • Spring 可以将只读提示传递给 JDBC 驱动
  • 在主从架构中,只读事务可以路由到从库

十一、事务超时

java 复制代码
// 设置事务超时为 10 秒
@Transactional(timeout = 10, rollbackFor = Exception.class)
public void processLargeData() {
    // 如果 10 秒内未完成,事务自动回滚
}

注意:超时计时从事务开始到最后一条 SQL 执行前。如果最后一条 SQL 本身执行很久,超时可能不会立即生效。


十二、方案对比总结

维度 @Transactional TransactionTemplate PlatformTransactionManager XML 配置
控制粒度 方法级 代码块级 代码块级 方法名模式
代码侵入
灵活性 最高
可读性 一般 一般
学习成本
推荐程度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐ ⭐⭐

十三、最佳实践清单

  1. 始终写 rollbackFor = Exception.class,避免 checked exception 不回滚
  2. 避免自调用:同类中方法互调不走代理,事务不生效
  3. 事务方法必须是 public
  4. 不要在事务方法中 catch 异常后不处理:要么重新抛出,要么手动标记回滚
  5. 事务范围尽量小:只包含必须原子性的操作,避免长事务
  6. 查询方法用 readOnly = true:提升性能,支持读写分离
  7. 设置合理的 timeout:防止长事务占用连接
  8. REQUIRES_NEW 谨慎使用:会占用额外连接,高并发下可能耗尽连接池
  9. 日志/通知等非核心操作放在事务外:避免影响主事务
  10. 多数据源场景显式指定事务管理器
相关推荐
金銀銅鐵11 小时前
[Python] 从《千字文》中随机挑选汉字
后端·python
cup1115 小时前
[技术复盘] Windows Python 打包实战:Nuitka 环境踩坑总结与 CI 自动化构建全指南
python·ai·环境变量·ci·nuitka·skill
aqi0017 小时前
15天学会AI应用开发(七)有了大模型为什么还要引入RAG
人工智能·python·大模型·ai编程·ai应用
金銀銅鐵19 小时前
用 Python 实现 Take-Away 游戏
python·游戏
copyer_xyf20 小时前
Agent 流程编排
后端·python·agent
copyer_xyf21 小时前
Agent RAG
后端·python·agent
copyer_xyf21 小时前
【RAG】向量数据库:milvus
后端·python·agent
copyer_xyf21 小时前
Agent 记忆管理
后端·python·agent
星云穿梭1 天前
用Python写一个带图形界面的学生管理系统——完整教程
python