JavaEE进阶——Spring事务与传播机制实战指南

目录

Spring事务和事务传播机制:新手超详细指南

一、事务核心概念(代码前的准备知识)

[1.1 什么是事务?(代码体现版)](#1.1 什么是事务?(代码体现版))

[1.2 为什么需要事务?(代码场景)](#1.2 为什么需要事务?(代码场景))

二、Spring中事务的实现方式

[2.1 编程式事务(详细注释版)](#2.1 编程式事务(详细注释版))

[2.2 声明式事务 @Transactional(推荐方式)](#2.2 声明式事务 @Transactional(推荐方式))

[三、@Transactional 注解详解](#三、@Transactional 注解详解)

[3.1 rollbackFor 属性(异常回滚详解)](#3.1 rollbackFor 属性(异常回滚详解))

[3.2 事务隔离级别](#3.2 事务隔离级别)

[3.2.1 MySQL隔离级别回顾(用代码理解)](#3.2.1 MySQL隔离级别回顾(用代码理解))

[3.2.2 Spring设置隔离级别](#3.2.2 Spring设置隔离级别)

[3.3 事务传播机制(重头戏)](#3.3 事务传播机制(重头戏))

[3.3.1 什么是事务传播机制?](#3.3.1 什么是事务传播机制?)

[3.3.2 7种传播机制全览](#3.3.2 7种传播机制全览)

[3.3.3 代码演示(完整带注释版)](#3.3.3 代码演示(完整带注释版))

[3.3.3.1 REQUIRED(加入事务)](#3.3.3.1 REQUIRED(加入事务))

[3.3.3.2 REQUIRES_NEW(新建事务)](#3.3.3.2 REQUIRES_NEW(新建事务))

[3.3.3.3 NEVER(不支持事务)](#3.3.3.3 NEVER(不支持事务))

[3.3.3.4 NESTED(嵌套事务)](#3.3.3.4 NESTED(嵌套事务))

[3.3.3.5 NESTED vs REQUIRED(局部回滚)](#3.3.3.5 NESTED vs REQUIRED(局部回滚))

四、完整项目代码(超详细注释版)

[4.1 pom.xml(依赖详细注释)](#4.1 pom.xml(依赖详细注释))

[4.2 application.properties(配置详细注释)](#4.2 application.properties(配置详细注释))

[4.3 实体类(详细注释)](#4.3 实体类(详细注释))

[4.4 Mapper接口(详细注释)](#4.4 Mapper接口(详细注释))

[4.5 Service层(详细注释)](#4.5 Service层(详细注释))

[4.6 Controller层(详细注释)](#4.6 Controller层(详细注释))

[4.7 数据库初始化脚本(详细注释)](#4.7 数据库初始化脚本(详细注释))

五、总结与代码对应关系

[5.1 Spring事务实现方式对比](#5.1 Spring事务实现方式对比)

[5.2 @Transactional关键属性代码体现](#5.2 @Transactional关键属性代码体现)

[5.3 事务传播机制总结表](#5.3 事务传播机制总结表)

六、常见问题与代码解答

Q1:为什么要在Service层而不是Controller层使用事务?

Q2:NESTED和REQUIRED有什么区别?

Q3:如何在事务中捕获异常并实现部分回滚?

七、扩展知识点

[7.1 事务ACID特性的代码体现](#7.1 事务ACID特性的代码体现)

[7.2 事务实现原理(AOP代理)](#7.2 事务实现原理(AOP代理))

八、最佳实践代码模板

最后的话

八、Spring事务失效场景详解(新手必看)

[8.1 同类自调用问题(最坑!)](#8.1 同类自调用问题(最坑!))

问题描述

错误代码示例

失效原因分析

正确解决方案

[8.2 方法修饰符非public](#8.2 方法修饰符非public)

问题描述

错误代码示例

正确解决方案

[8.3 方法是final/static](#8.3 方法是final/static)

问题描述

错误代码示例

正确解决方案

[8.4 多线程调用问题](#8.4 多线程调用问题)

问题描述

错误代码示例

正确解决方案

[8.5 异常被catch未抛出](#8.5 异常被catch未抛出)

问题描述

错误代码示例

正确解决方案

[8.6 数据库引擎不支持事务](#8.6 数据库引擎不支持事务)

问题描述

问题代码(SQL层面)

正确解决方案

[8.7 未配置事务管理器](#8.7 未配置事务管理器)

问题描述

[错误配置(Spring Boot)](#错误配置(Spring Boot))

正确配置

[8.8 传播机制设置错误](#8.8 传播机制设置错误)

问题描述

错误代码示例

正确解决方案

[8.9 总结:事务失效检查清单](#8.9 总结:事务失效检查清单)


Spring事务和事务传播机制:新手超详细指南

我会为你重新梳理整个Spring事务知识体系,重点补充代码与知识点的对应关系 ,并对所有代码进行逐行详细注释

一、事务核心概念(代码前的准备知识)

1.1 什么是事务?(代码体现版)

事务就是一组"要么一起成功,要么一起失败"的数据库操作。来看一个没有事务的灾难场景:

java 复制代码
// 没有事务的转账方法(危险!)
public void transferMoney(String fromAccount, String toAccount, int amount) {
    // 第一步:A账户扣钱
    jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE user_id = ?", amount, fromAccount);
    
    // 第二步:B账户加钱
    // 如果这里发生异常(比如网络断开),A的钱已经扣了,但B没收到!
    int i = 10 / 0; // 模拟异常
    
    jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE user_id = ?", amount, toAccount);
}

对应知识点:这个例子说明,如果没有事务保护,两个操作之间发生异常会导致数据不一致。A账户的钱凭空消失。

1.2 为什么需要事务?(代码场景)

看三个真实业务场景:

场景1:转账操作(上面已演示)

场景2:秒杀系统

java 复制代码
// 没有事务的秒杀方法(会导致超卖!)
public void seckill(String productId) {
    // 第一步:创建订单(成功)
    orderMapper.createOrder(productId, userId);
    
    // 第二步:扣减库存(失败)
    // 如果库存扣减失败,但订单已创建,就超卖了!
    stockMapper.reduceStock(productId); // 假设这里抛异常
}

场景3:订单创建

java 复制代码
// 没有事务的订单创建(数据不完整)
public void createOrder(Order order) {
    // 主订单表插入成功
    orderMapper.insertMainOrder(order);
    
    // 订单明细表插入失败(比如某个明细数据格式错误)
    orderMapper.insertOrderItems(order.getItems()); // 抛出异常
    
    // 库存更新没执行
    // 结果:数据库中只有主订单,没有明细,数据不完整!
}

二、Spring中事务的实现方式

2.1 编程式事务(详细注释版)

这是最原始但最灵活的方式,适合需要精细控制事务的场景。

java 复制代码
// 1. 导入Spring事务管理核心类
// Spring提供的JDBC事务管理器,负责实际的事务操作(开启、提交、回滚)
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

// 事务定义接口,用于设置事务属性(隔离级别、传播行为、超时时间等)
import org.springframework.transaction.TransactionDefinition;

// 事务状态接口,表示当前事务的状态,用于提交或回滚
import org.springframework.transaction.TransactionStatus;

// Spring提供的模板类,简化编程式事务的写法(后面会介绍)
import org.springframework.transaction.support.TransactionTemplate;

// Spring MVC注解,标识这是一个处理HTTP请求的控制器
import org.springframework.web.bind.annotation.RequestMapping;

// Spring MVC注解,标识控制器返回的数据直接写入HTTP响应体(RESTful风格)
import org.springframework.web.bind.annotation.RestController;

// Spring依赖注入注解,自动将Spring容器中的Bean注入到字段
import org.springframework.beans.factory.annotation.Autowired;

// 业务服务类(处理用户注册逻辑)
import com.example.demo.service.UserService;

/**
 * 用户注册控制器 - 演示编程式事务
 * 这是最原始的事务管理方式,手动控制事务的每一个步骤
 */
@RequestMapping("/user")  // 设置该控制器的基础请求路径为/user
@RestController           // 声明这是一个RESTful控制器,所有方法返回JSON数据
public class UserController {

    // 2. 注入事务管理器(核心组件)
    // DataSourceTransactionManager是Spring提供的JDBC事务管理器
    // 它负责:获取数据库连接、开启事务、提交事务、回滚事务
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    // 3. 注入事务定义(设置事务属性)
    // TransactionDefinition用于定义事务的行为特性
    // 比如:隔离级别、传播行为、超时时间、是否只读等
    @Autowired
    private TransactionDefinition transactionDefinition;

    // 4. 注入用户服务(执行业务逻辑)
    // UserService包含具体的业务操作(如插入用户数据)
    @Autowired
    private UserService userService;

    /**
     * 用户注册接口 - 使用编程式事务
     * @param name 用户名
     * @param password 密码
     * @return 注册结果
     */
    @RequestMapping("/registry")  // 映射HTTP请求路径为/user/registry
    public String registry(String name, String password) {
        
        // 5. 【核心】开启事务
        // getTransaction()方法:根据transactionDefinition的定义,开启一个新事务
        // 返回TransactionStatus对象,用于追踪当前事务状态
        // 相当于执行SQL: START TRANSACTION;
        TransactionStatus transactionStatus = 
            dataSourceTransactionManager.getTransaction(transactionDefinition);

        try {
            // 6. 在事务中执行业务逻辑
            // 如果这里抛出RuntimeException,事务会自动回滚
            userService.registryUser(name, password);

            // 7. 【核心】提交事务
            // 如果try块中的所有操作都成功,提交事务
            // 相当于执行SQL: COMMIT;
            // 所有对数据库的修改永久生效
            dataSourceTransactionManager.commit(transactionStatus);

            return "注册成功";
            
        } catch (Exception e) {
            // 8. 【核心】回滚事务
            // 如果try块中任何代码抛出异常,捕获后回滚事务
            // 相当于执行SQL: ROLLBACK;
            // 所有在事务中的数据库操作都撤销,就像从未执行过
            dataSourceTransactionManager.rollback(transactionStatus);
            
            // 重新抛出异常,让Spring框架处理(比如返回500错误)
            throw e; 
        }
    }
}

知识点与代码的对应关系

|---------|-------------------------|---------|
| 知识点 | 代码体现 | 关键行 |
| 开启事务 | getTransaction() | 第55行 |
| 提交事务 | commit() | 第65行 |
| 回滚事务 | rollback() | 第72行 |
| 事务属性设置 | TransactionDefinition | 第31行 |
| 事务状态追踪 | TransactionStatus | 第55行 |

2.2 声明式事务 @Transactional(推荐方式)

这是实际开发中最常用的方式,通过注解自动管理事务。

第一步:添加依赖(pom.xml)

XML 复制代码
<!-- Spring事务管理依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>

第二步:使用注解

java 复制代码
// 导入Spring事务注解
// 声明式事务的核心注解,加在方法或类上自动开启事务管理
import org.springframework.transaction.annotation.Transactional;

// 导入其他必要类
import org.springframework.beans.factory.annotation.Autowired;  // 依赖注入
import org.springframework.web.bind.annotation.RequestMapping;    // 请求映射
import org.springframework.web.bind.annotation.RestController;    // REST控制器

/**
 * 用户注册控制器 - 演示声明式事务
 * 这种方式无需手动编写事务管理代码,Spring通过AOP自动代理实现
 */
@RequestMapping("/user")  // 基础请求路径
@RestController           // RESTful控制器
public class TransactionalController {

    // 注入用户服务
    @Autowired
    private UserService userService;

    /**
     * 用户注册接口 - 声明式事务
     * * @Transactional注解的作用:
     * 1. 在方法执行前自动开启事务(相当于getTransaction())
     * 2. 在方法成功执行后自动提交事务(相当于commit())
     * 3. 在方法抛出未捕获异常时自动回滚事务(相当于rollback())
     * * 注意:默认只对RuntimeException和Error回滚
     */
    @Transactional  // 核心注解!加在public方法上才有效
    @RequestMapping("/registry")  // 映射请求路径
    public String registry(String name, String password) {
        // Spring会在调用此方法前自动开启事务
        // 方法内的所有数据库操作都在同一个事务中
        
        userService.registryUser(name, password);  // 插入用户信息
        
        // 如果这里抛出RuntimeException,整个事务会自动回滚
        // 模拟异常:int i = 10/0;
        
        return "注册成功";  // 方法正常返回,Spring自动提交事务
    }
}

代码与知识点的桥梁

java 复制代码
// 编程式事务的繁琐写法
public void oldWay() {
    TransactionStatus status = manager.getTransaction(definition); // 手动开启
    try {
        // 业务代码
        manager.commit(status);  // 手动提交
    } catch(Exception e) {
        manager.rollback(status); // 手动回滚
        throw e;
    }
}

// 声明式事务的简洁写法(Spring AOP自动代理)
@Transactional  // 一个注解替代所有手动代码!
public void newWay() {
    // 业务代码
    // Spring自动处理开启、提交、回滚
}

三、@Transactional 注解详解

3.1 rollbackFor 属性(异常回滚详解)

问题演示代码

java 复制代码
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import import java.io.IOException;  // 导入IO异常类(检查型异常)

/**
 * 演示默认回滚机制的缺陷
 */
@RestController
public class RollbackDemoController {

    /**
     * 默认事务配置
     * 问题:当抛出IOException(检查型异常)时,事务不会回滚!
     * 原因:Spring默认只对RuntimeException和Error回滚
     */
    @Transactional  // 没有指定rollbackFor
    @RequestMapping("/r2")
    public String r2(String name, String password) throws IOException {
        userService.registryUser(name, password);  // 插入用户成功
        
        log.info("用户数据插入成功");
        
        // 抛出检查型异常IOException(不是RuntimeException)
        // Spring会认为"这不是我需要管的异常",所以不会回滚事务!
        if (true) {
            throw new IOException("网络异常");  // 数据库操作不会回滚!
        }
        
        return "r2";
    }
    
    /**
     * 正确的事务配置
     * 解决方案:通过rollbackFor指定需要回滚的异常类型
     */
    @Transactional(
        rollbackFor = Exception.class  // 指定所有Exception都回滚
        // rollbackFor = {IOException.class, SQLException.class}  // 也可以指定多个
    )
    @RequestMapping("/r3")
    public String r3(String name, String password) throws IOException {
        userService.registryUser(name, password);
        
        log.info("用户数据插入成功");
        
        // 现在抛出IOException也会回滚了!
        if (true) {
            throw new IOException("网络异常");
        }
        
        return "r3";  // 这行不会执行,因为上面抛异常了
    }
}

结论与代码的对应关系

|---------------------|-------------------------------------------------|-----------------|
| 默认行为 | 代码体现 | 结果 |
| 只回滚RuntimeException | @Transactional(无参数) | IOException不会回滚 |
| 回滚所有异常 | @Transactional(rollbackFor = Exception.class) | 所有异常都会回滚 |

3.2 事务隔离级别

3.2.1 MySQL隔离级别回顾(用代码理解)
sql 复制代码
-- 查询当前MySQL隔离级别
SELECT @@global.tx_isolation, @@tx_isolation;
-- 结果示例:REPEATABLE-READ(MySQL默认)
3.2.2 Spring设置隔离级别
java 复制代码
import org.springframework.transaction.annotation.Isolation;  // 导入隔离级别枚举
import org.springframework.transaction.annotation.Transactional;

/**
 * 隔离级别演示
 */
@RestController
public class IsolationDemoController {

    /**
     * 设置隔离级别为READ_COMMITTED
     * * 解决:脏读问题(读取到未提交的数据)
     * 场景:适合读多写少的场景,提高并发性能
     * * 代码中的体现:
     * @Transactional(isolation = ...) 指定隔离级别
     */
    @Transactional(
        isolation = Isolation.READ_COMMITTED  // 读已提交
    )
    @RequestMapping("/r3")
    public String r3(String name, String password) {
        // 在这个事务中,只能读取到其他事务已提交的数据
        // 避免了脏读,但可能出现不可重复读
        return "r3";
    }
    
    /**
     * 使用数据库默认隔离级别
     * * Isolation.DEFAULT = -1,表示"跟随数据库默认设置"
     * MySQL下等效于Isolation.REPEATABLE_READ
     */
    @Transactional(
        isolation = Isolation.DEFAULT  // 使用数据库默认(推荐)
    )
    @RequestMapping("/r4")
    public String r4(String name, String password) {
        return "r4";
    }
}

知识点与代码的直接对应

java 复制代码
// 隔离级别在代码中的体现就是这一个参数
@Transactional(
    isolation = Isolation.READ_UNCOMMITTED  // 读未提交,最低级别,可能脏读
    // isolation = Isolation.READ_COMMITTED   // 读已提交,避免脏读
    // isolation = Isolation.REPEATABLE_READ  // 可重复读,避免不可重复读(MySQL默认)
    // isolation = Isolation.SERIALIZABLE     // 串行化,最高级别,避免幻读但性能差
)

3.3 事务传播机制(重头戏)

3.3.1 什么是事务传播机制?

场景代码

java 复制代码
// 主方法(带事务)
@Transactional
public void methodA() {
    // 调用另一个带事务的方法
    serviceB.methodB();  // 问题:B是加入A的事务,还是创建自己的事务?
}

代码体现 :传播机制就是解决methodB该如何处理事务的问题。

3.3.2 7种传播机制全览
java 复制代码
// Spring源码中的Propagation枚举
public enum Propagation {
    REQUIRED(0),      // 默认值,如果存在事务则加入,否则新建
    SUPPORTS(1),      // 如果存在事务则加入,否则非事务方式运行
    MANDATORY(2),     // 强制要求存在事务,否则抛异常
    REQUIRES_NEW(3),  // 总是新建事务,挂起当前事务
    NOT_SUPPORTED(4), // 以非事务方式运行,挂起当前事务
    NEVER(5),         // 以非事务方式运行,如果存在事务则抛异常
    NESTED(6);        // 如果存在事务则创建嵌套事务
}
3.3.3 代码演示(完整带注释版)

场景说明:注册用户信息 + 记录日志,两个操作都需要事务,但日志记录失败不应该影响用户注册。

3.3.3.1 REQUIRED(加入事务)
java 复制代码
// =============== Controller层 ===============
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 事务传播机制测试控制器
 * 场景:用户注册时记录日志
 */
@RequestMapping("/propaga")  // 基础路径
@RestController              // REST控制器
public class PropagationController {

    // 注入用户服务
    @Autowired
    private UserService userService;
    
    // 注入日志服务
    @Autowired
    private LogService logService;

    /**
     * 测试REQUIRED传播机制
     * * REQUIRED的行为:
     * 1. 如果methodA()调用methodB()时,A已经有事务
     * 2. 那么B会加入A的事务,不会创建新事务
     * 3. 整个事务要么全部成功,要么全部失败
     * * 代码体现:
     * @Transactional(propagation = Propagation.REQUIRED)
     */
    @Transactional(propagation = Propagation.REQUIRED)  // 开启主事务
    @RequestMapping("/p1")
    public String p1(String name, String password) {
        // 1. 注册用户(加入当前事务)
        userService.registryUser(name, password);  // 使用主事务
        
        // 2. 记录日志(加入当前事务)
        logService.insertLog(name, "用户注册");    // 使用主事务
        
        // 3. 如果insertLog()抛出异常,整个事务(包括registryUser)都会回滚
        return "p1";
    }
}

// =============== UserService层 ===============
import com.example.demo.mapper.UserInfoMapper;  // 用户Mapper
import lombok.extern.slf4j.Slf4j;              // 日志注解
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;  // 服务层注解
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 用户服务类
 * 负责用户相关的业务逻辑
 */
@Slf4j  // 自动生成日志对象log,用于打印日志
@Service  // 声明这是一个服务层组件,Spring会自动扫描并注册为Bean
public class UserService {

    // 注入用户Mapper(操作数据库)
    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * 注册用户方法
     * * 传播机制:REQUIRED(加入当前事务)
     * 行为:如果调用者已经开启事务,就加入该事务;否则新建
     * * 代码体现:
     * @Transactional(propagation = Propagation.REQUIRED)
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void registryUser(String name, String password) {
        // 插入用户信息到数据库
        // 这个方法没有自己的事务,它加入调用者的事务
        userInfoMapper.insert(name, password);
        
        log.info("用户数据插入成功");  // 记录成功日志
    }
}

// =============== LogService层 ===============
import com.example.demo.mapper.LogInfoMapper;  // 日志Mapper
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 日志服务类
 * 负责记录操作日志
 */
@Slf4j
@Service
public class LogService {

    // 注入日志Mapper
    @Autowired
    private LogInfoMapper logInfoMapper;

    /**
     * 插入日志方法
     * * 传播机制:REQUIRED(加入当前事务)
     * 问题:如果这里抛出异常,整个事务都会回滚
     * 包括已经成功的registryUser()!
     * * 代码体现:在方法中主动抛出异常
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertLog(String name, String op) {
        // 模拟异常:除零错误(RuntimeException)
        // 这会导致整个事务回滚,包括用户注册信息
        int a = 10 / 0;  // ❌ 抛出ArithmeticException
        
        // 这行代码不会执行,因为上面已经抛异常
        logInfoMapper.insertLog(name, op);  // 记录日志
    }
}

执行结果分析

sql 复制代码
// 调用链:
p1() [主事务开始] 
  → registryUser() [加入主事务] → 插入用户成功
  → insertLog() [加入主事务] → 抛出异常
  → 主事务回滚 → 用户数据也被回滚!

// 数据库结果:用户表和日志表都**没有**数据
// 原因:REQUIRED让所有方法共享同一个事务

知识点与代码的对应关系

|----------|------------------------------------------------------|-----------------------|
| 传播机制 | 代码体现 | 执行结果 |
| REQUIRED | @Transactional(propagation = Propagation.REQUIRED) | 所有操作在同一个事务中,一荣俱荣,一损俱损 |

3.3.3.2 REQUIRES_NEW(新建事务)

修改代码

java 复制代码
// =============== UserService层修改 ===============
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * 传播机制:REQUIRES_NEW(新建事务)
     * 行为:总是创建新事务,挂起调用者的事务
     * * 代码体现:
     * @Transactional(propagation = Propagation.REQUIRES_NEW)
     * * 执行流程:
     * 1. p1()开启主事务
     * 2. registryUser()发现REQUIRES_NEW,挂起主事务,创建新事务
     * 3. 在新事务中执行插入用户操作
     * 4. 新事务提交,主事务被恢复
     * 5. insertLog()抛出异常,只回滚自己的事务
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password);  // 在新事务中执行
        log.info("用户数据插入成功");
    }
}

// =============== LogService层修改 ===============
@Slf4j
@Service
public class LogService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    /**
     * 传播机制:REQUIRES_NEW(新建事务)
     * 行为:总是创建新事务,挂起调用者的事务
     * * 代码体现:
     * @Transactional(propagation = Propagation.REQUIRES_NEW)
     * * 执行流程:
     * 1. 主事务被挂起
     * 2. 创建新的事务执行日志插入
     * 3. 抛出异常,只回滚当前新事务
     * 4. 恢复主事务
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertLog(String name, String op) {
        int a = 10 / 0;  // 在新事务中抛异常
        logInfoMapper.insertLog(name, op);
    }
}

执行结果分析

java 复制代码
// 调用链:
p1() [主事务开始] 
  → registryUser() [挂起主事务,创建新事务] → 插入用户成功 → **新事务提交** → 恢复主事务
  → insertLog() [挂起主事务,创建新事务] → 抛出异常 → **新事务回滚** → 恢复主事务
  → 主事务提交

// 数据库结果:用户表**有**数据,日志表**没有**数据
// 原因:REQUIRES_NEW让每个方法独立事务,互不影响

知识点与代码的对应关系

|--------------|----------------------------------------------------------|--------------------------|
| 传播机制 | 代码体现 | 执行结果 |
| REQUIRES_NEW | @Transactional(propagation = Propagation.REQUIRES_NEW) | 方法独立事务,成功的方法提交,失败的方法单独回滚 |

3.3.3.3 NEVER(不支持事务)
java 复制代码
@Slf4j
@Service
public class UserService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * 传播机制:NEVER(不支持事务)
     * 行为:以非事务方式运行,如果调用者存在事务,则抛出异常
     * * 代码体现:
     * @Transactional(propagation = Propagation.NEVER)
     * * 执行流程:
     * 1. p1()开启了主事务
     * 2. registryUser()被调用,但传播机制是NEVER
     * 3. 由于当前存在事务,Spring抛出异常:
     * "Existing transaction found for transaction marked with propagation 'never'"
     * 4. 整个操作失败
     */
    @Transactional(propagation = Propagation.NEVER)
    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password);
    }
}

执行结果

复制代码
// 抛出异常:Existing transaction found for transaction marked with propagation 'never'
// NESTED与REQUIRED区别(最关键!)
3.3.3.4 NESTED(嵌套事务)
java 复制代码
// =============== UserService层修改 ===============
@Slf4j
@Service
public class UserService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * 传播机制:NESTED(嵌套事务)
     * 行为:如果调用者存在事务,则创建嵌套事务(子事务)
     * * 代码体现:
     * @Transactional(propagation = Propagation.NESTED)
     * * 技术原理:
     * 嵌套事务使用数据库的Savepoint(保存点)实现
     * 相当于:SAVEPOINT savepoint1;  // 创建保存点
     * 回滚时:ROLLBACK TO SAVEPOINT savepoint1;  // 回滚到保存点
     * * 执行流程:
     * 1. p1()开启主事务
     * 2. registryUser()创建嵌套事务(保存点1)
     * 3. insertLog()创建嵌套事务(保存点2)
     * 4. insertLog()抛出异常,回滚到保存点2
     * 5. 异常向上抛出,导致主事务也回滚
     * 6. 结果:所有数据都回滚
     */
    @Transactional(propagation = Propagation.NESTED)
    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password);
        log.info("用户数据插入成功");
    }
}

// =============== LogService层修改 ===============
@Slf4j
@Service
public class LogService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    /**
     * 传播机制:NESTED(嵌套事务)
     * 行为:创建嵌套事务
     * * 执行流程:
     * 1. 在主事务中创建保存点
     * 2. 在嵌套事务中执行操作
     * 3. 抛出异常,嵌套事务回滚
     * 4. 异常继续抛出,主事务也回滚
     */
    @Transactional(propagation = Propagation.NESTED)
    public void insertLog(String name, String op) {
        int a = 10 / 0;  // 嵌套事务内抛异常
        logInfoMapper.insertLog(name, op);
    }
}
3.3.3.5 NESTED vs REQUIRED(局部回滚)

关键区别代码演示

java 复制代码
// =============== LogService层 - NESTED实现局部回滚 ===============
import org.springframework.transaction.interceptor.TransactionAspectSupport;  // 手动回滚工具类
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
public class LogService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    /**
     * NESTED传播机制 + 手动回滚 = 局部回滚
     * * 执行流程:
     * 1. p1()开启主事务
     * 2. registryUser()在主事务中执行成功
     * 3. insertLog()创建嵌套事务(保存点)
     * 4. 发生异常,捕获后在catch块中手动回滚嵌套事务
     * 5. 主事务继续执行,不受影响,最终提交
     * * 代码关键点:
     * - NESTED传播机制
     * - catch块中调用setRollbackOnly()
     */
    @Transactional(propagation = Propagation.NESTED)  // 必须是NESTED
    public void insertLog(String name, String op) {
        try {
            // 业务逻辑
            int a = 10 / 0;  // 模拟异常
            
            // 正常插入日志
            logInfoMapper.insertLog(name, op);
            log.info("日志记录成功");
            
        } catch (Exception e) {
            // 【核心】手动回滚当前嵌套事务
            // 只回滚insertLog这个嵌套事务,不影响主事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            
            log.error("日志记录失败,但主事务继续执行", e);
            // 注意:这里catch了异常,主事务感知不到,所以不会回滚
        }
    }
}

对比REQUIRED的代码

复制代码
// 如果改为REQUIRED,同样的代码会导致整个事务回滚
@Transactional(propagation = Propagation.REQUIRED)  // 改为REQUIRED
public void insertLog(String name, String op) {
    try {
        int a = 10 / 0;
        logInfoMapper.insertLog(name, op);
    } catch (Exception e) {
        // 即使是REQUIRED,在catch中setRollbackOnly也会标记整个事务为回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        // 主事务最终也会回滚!
    }
}

知识点与代码的对应关系

|----------|------------------------------------------------------|-----------------------|---------------|
| 传播机制 | 代码体现 | 异常处理方式 | 结果 |
| REQUIRED | @Transactional(propagation = Propagation.REQUIRED) | catch中setRollbackOnly | 整个事务回滚 |
| NESTED | @Transactional(propagation = Propagation.NESTED) | catch中setRollbackOnly | 仅嵌套事务回滚,主事务继续 |

四、完整项目代码(超详细注释版)

4.1 pom.xml(依赖详细注释)

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!--
Maven项目对象模型文件
定义项目信息、依赖管理、构建配置等
-->
<project xmlns="[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0)"
         xmlns:xsi="[http://www.w3.org/2001/XMLSchema-instance](http://www.w3.org/2001/XMLSchema-instance)"
         xsi:schemaLocation="[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0) 
         [https://maven.apache.org/xsd/maven-4.0.0.xsd](https://maven.apache.org/xsd/maven-4.0.0.xsd)">
    
    <!-- Maven模型版本,固定为4.0.0 -->
    <modelVersion>4.0.0</modelVersion>
    
    <!-- 父项目配置:spring-boot-starter-parent
         作用:提供Spring Boot的默认配置、依赖版本管理、插件配置等
         好处:无需手动指定每个依赖的版本号
    -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>  <!-- Spring Boot版本 -->
        <relativePath/>  <!-- 不从本地路径查找父项目 -->
    </parent>
    
    <!-- 本项目信息 -->
    <groupId>com.example</groupId>      <!-- 组织ID -->
    <artifactId>spring-trans</artifactId>  <!-- 项目ID -->
    <version>0.0.1-SNAPSHOT</version>   <!-- 版本号 -->
    <name>spring-trans</name>           <!-- 项目名称 -->
    <description>Spring事务演示项目</description>  <!-- 描述 -->
    
    <!-- 属性配置 -->
    <properties>
        <!-- Java版本 -->
        <java.version>11</java.version>
    </properties>
    
    <!-- 依赖列表 -->
    <dependencies>
        <!-- Spring Web Starter
             包含:Spring MVC、Tomcat、JSON处理等
             作用:提供Web应用基础功能
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- MyBatis Spring Boot Starter
             包含:MyBatis、Spring JDBC、连接池等
             作用:简化MyBatis与Spring Boot的集成
        -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>  <!-- 指定版本 -->
        </dependency>
        
        <!-- MySQL驱动
             scope: runtime表示编译时不依赖,运行时由容器提供
        -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>  <!-- 只在运行时需要 -->
        </dependency>
        
        <!-- Lombok
             作用:通过注解减少样板代码(getter、setter、toString等)
             optional: true表示不强制依赖
        -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- Spring事务
             虽然starter-web已包含,但显式声明更清晰
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
    </dependencies>
    
    <!-- 构建配置 -->
    <build>
        <plugins>
            <!-- Spring Boot Maven插件
                 作用:支持打包可执行jar、运行Spring Boot应用
            -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <!-- 打包时排除Lombok -->
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4.2 application.properties(配置详细注释)

java 复制代码
# ==================== 数据库配置 ====================
# MySQL数据库连接URL
# 参数说明:
# - characterEncoding=utf8:字符编码为UTF-8
# - useSSL=false:不使用SSL连接
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=false

# 数据库用户名
spring.datasource.username=root

# 数据库密码
spring.datasource.password=root

# MySQL驱动类名(8.0+版本)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# ==================== MyBatis配置 ====================
# 日志实现类:在控制台输出SQL语句
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

# 自动将下划线命名转换为驼峰命名
# 例如:user_name字段 → userName属性
mybatis.configuration.map-underscore-to-camel-case=true

4.3 实体类(详细注释)

java 复制代码
// =============== UserInfo.java ===============
import lombok.Data;  // Lombok注解,自动生成getter、setter、toString、equals、hashCode

import java.util.Date;  // 日期类

/**
 * 用户信息实体类
 * 对应数据库表:user_info
 * * Lombok的@Data注解等价于:
 * - @Getter:生成所有字段的get方法
 * - @Setter:生成所有字段的set方法
 * - @ToString:生成toString方法
 * - @EqualsAndHashCode:生成equals和hashCode方法
 * - @RequiredArgsConstructor:生成必填字段的构造方法
 */
@Data  // 使用Lombok减少样板代码
public class UserInfo {
    
    // 主键ID
    private Integer id;
    
    // 用户名(对应表字段user_name)
    private String userName;
    
    // 密码
    private String password;
    
    // 创建时间
    private Date createTime;
    
    // 更新时间
    private Date updateTime;
}

// =============== LogInfo.java ===============
import lombok.Data;
import java.util.Date;

/**
 * 日志信息实体类
 * 对应数据库表:log_info
 */
@Data
public class LogInfo {
    
    // 主键ID
    private Integer id;
    
    // 用户名
    private String userName;
    
    // 操作类型
    private String op;
    
    // 创建时间
    private Date createTime;
    
    // 更新时间
    private Date updateTime;
}

4.4 Mapper接口(详细注释)

java 复制代码
// =============== UserInfoMapper.java ===============
import org.apache.ibatis.annotations.Insert;  // MyBatis插入注解
import org.apache.ibatis.annotations.Mapper;   // MyBatis Mapper标识注解

/**
 * 用户信息Mapper接口
 * 作用:定义操作user_info表的方法
 * * @Mapper注解:告诉MyBatis这是Mapper接口,生成代理实现类
 */
@Mapper  // 必须加,否则Spring无法扫描到
public interface UserInfoMapper {
    
    /**
     * 插入用户信息
     * * @Insert注解:直接写SQL语句,无需XML配置
     * #{name}:MyBatis的占位符,会被方法参数name替换
     * * @param name 用户名
     * @param password 密码
     * @return 插入的行数(成功返回1,失败返回0)
     */
    @Insert("INSERT INTO user_info(user_name, password) VALUES(#{name}, #{password})")
    Integer insert(String name, String password);
}

// =============== LogInfoMapper.java ===============
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

/**
 * 日志信息Mapper接口
 * 作用:定义操作log_info表的方法
 */
@Mapper
public interface LogInfoMapper {
    
    /**
     * 插入日志信息
     * * @param name 用户名
     * @param op 操作描述
     * @return 插入的行数
     */
    @Insert("INSERT INTO log_info(user_name, op) VALUES(#{name}, #{op})")
    Integer insertLog(String name, String op);
}

4.5 Service层(详细注释)

java 复制代码
// =============== UserService.java ===============
import com.example.demo.mapper.UserInfoMapper;  // 用户Mapper
import com.example.demo.model.UserInfo;         // 用户实体
import lombok.extern.slf4j.Slf4j;              // Lombok日志注解
import org.springframework.beans.factory.annotation.Autowired;  // 依赖注入
import org.springframework.stereotype.Service;  // 服务层注解
import org.springframework.transaction.annotation.Propagation;    // 传播机制枚举
import org.springframework.transaction.annotation.Transactional;  // 事务注解

/**
 * 用户服务类
 * 处理用户注册等业务逻辑
 * * @Service注解:标识这是一个服务层组件
 * 作用:1. 被Spring扫描注册为Bean 2. 支持AOP代理(事务)
 */
@Slf4j  // 自动生成log日志对象
@Service
public class UserService {

    // 注入UserInfoMapper(数据库操作)
    // @Autowired:Spring自动装配,将Spring容器中的Mapper注入
    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * 注册用户核心业务方法
     * * @Transactional注解:启动声明式事务
     * propagation = Propagation.REQUIRED:如果调用者有事务就加入,否则新建
     * * 重要:Service层是事务的最佳放置位置
     * 原因:Service层代表业务逻辑,一个业务可能包含多个DAO操作
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void registryUser(String name, String password) {
        // 调用Mapper插入数据
        // 如果插入失败(抛异常),事务会自动回滚
        userInfoMapper.insert(name, password);
        
        // 记录成功日志
        // {}是占位符,会被后面的参数替换,比字符串拼接性能更好
        log.info("用户数据插入成功:用户名={}", name);
    }
}

// =============== LogService.java ===============
import com.example.demo.mapper.LogInfoMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;  // 手动回滚工具

/**
 * 日志服务类
 * 负责记录用户操作日志
 * * 设计思路:日志记录不应该影响主业务
 * 即使日志记录失败,用户注册也应该成功
 * 因此使用NESTED实现局部回滚
 */
@Slf4j
@Service
public class LogService {

    // 注入LogInfoMapper
    @Autowired
    private LogInfoMapper logInfoMapper;

    /**
     * 插入日志方法
     * * @Transactional注解:
     * propagation = Propagation.NESTED:创建嵌套事务
     * * NESTED的特殊能力:
     * 1. 可以捕获异常并手动回滚嵌套事务
     * 2. 不影响父事务的提交
     * * 对比REQUIRED:
     * 如果是REQUIRED,即使catch了异常,只要setRollbackOnly(),
     * 整个事务(包括父事务)都会回滚
     */
    @Transactional(propagation = Propagation.NESTED)
    public void insertLog(String name, String op) {
        try {
            // 模拟业务逻辑
            log.info("开始记录日志:用户={}, 操作={}", name, op);
            
            // 模拟异常(比如日志服务器不可用)
            int a = 10 / 0;  // 抛出ArithmeticException
            
            // 正常插入日志
            logInfoMapper.insertLog(name, op);
            log.info("日志记录成功");
            
        } catch (Exception e) {
            // 【核心】手动回滚嵌套事务
            // TransactionAspectSupport.currentTransactionStatus()获取当前事务状态
            // setRollbackOnly()标记当前事务为"仅回滚"
            // 对于NESTED,只回滚嵌套事务;对于REQUIRED,标记整个事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            
            // 记录错误日志,但不抛出异常
            // 这样父事务就不会感知到异常,会继续提交
            log.error("日志记录失败,但主业务不受影响", e);
        }
    }
}

4.6 Controller层(详细注释)

java 复制代码
// =============== TransactionalController.java ===============
import com.example.demo.service.LogService;   // 日志服务
import com.example.demo.service.UserService;  // 用户服务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;    // 传播机制
import org.springframework.transaction.annotation.Transactional;  // 事务注解
import org.springframework.web.bind.annotation.RequestMapping;    // 请求映射
import org.springframework.web.bind.annotation.RestController;    // REST控制器

/**
 * 事务测试控制器
 * * @RestController = @Controller + @ResponseBody
 * 作用:1. 声明控制器 2. 所有方法返回JSON数据
 * * @RequestMapping("/trans"):
 * 类级别的路径映射,所有方法路径前都有/trans前缀
 */
@RestController
@RequestMapping("/trans")
public class TransactionalController {

    // 注入用户服务
    @Autowired
    private UserService userService;
    
    // 注入日志服务
    @Autowired
    private LogService logService;

    /**
     * 注册用户并记录日志
     * * 完整业务流程:
     * 1. 开启主事务(REQUIRED)
     * 2. 注册用户(NESTED嵌套事务)
     * 3. 记录日志(NESTED嵌套事务,可局部回滚)
     * 4. 主事务提交
     * * @param name 用户名
     * @param password 密码
     * @return 操作结果
     */
    @RequestMapping("/registry")  // 完整路径:/trans/registry
    @Transactional(propagation = Propagation.REQUIRED)  // 开启主事务
    public String registry(String name, String password) {
        
        // 第一步:注册用户
        // 如果UserService是NESTED,这里创建嵌套事务
        // 如果UserService是REQUIRED,这里加入当前事务
        userService.registryUser(name, password);
        
        // 第二步:记录日志
        // 即使日志记录失败(被catch),也不会影响主事务
        // 因为LogService中catch了异常并setRollbackOnly()
        logService.insertLog(name, "用户注册");
        
        // 方法正常返回,主事务自动提交
        // 如果抛出RuntimeException,主事务自动回滚
        return "注册成功";
    }
}

完整调用流程注释

html 复制代码
// 浏览器请求:GET /trans/registry?name=Tom&password=123

// Spring MVC处理流程:
// 1. DispatcherServlet接收请求
// 2. 找到TransactionalController.registry()方法
// 3. 发现@Transactional注解,创建代理对象
// 4. 代理对象执行:
//   a. 开启主事务(REQUIRED)
//   b. 调用registry()方法
//      i. userService.registryUser() → 嵌套事务,插入用户成功
//      ii. logService.insertLog() → 嵌套事务,抛出异常但被catch
//          - 在catch中setRollbackOnly() → 只回滚嵌套事务
//          - 异常被捕获,不向上抛出
//   c. registry()方法正常结束
//   d. 提交主事务(用户数据保留)
// 5. 返回"注册成功"

// 数据库结果:
// user_info表:Tom用户插入成功
// log_info表:没有日志(因为嵌套事务回滚了)

4.7 数据库初始化脚本(详细注释)

sql 复制代码
-- =============== trans_test.sql ===============

-- 删除已存在的数据库(如果存在)
-- 用于重新初始化测试环境
DROP DATABASE IF EXISTS trans_test;

-- 创建新数据库
-- DEFAULT CHARACTER SET utf8mb4:设置默认字符集为UTF-8(支持emoji)
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;

-- ==================== 用户表 ====================
-- 删除已存在的表
DROP TABLE IF EXISTS user_info;

-- 创建用户表
CREATE TABLE user_info(
    -- 主键ID,自增
    id INT NOT NULL AUTO_INCREMENT,
    
    -- 用户名,VARCHAR(128)表示最大128字符,NOT NULL表示不能为空
    user_name VARCHAR(128) NOT NULL,
    
    -- 密码,VARCHAR(128)
    password VARCHAR(128) NOT NULL,
    
    -- 创建时间,DATETIME类型,DEFAULT now()表示默认当前时间
    create_time DATETIME DEFAULT now(),
    
    -- 更新时间,ON UPDATE now()表示每次更新记录时自动更新为当前时间
    update_time DATETIME DEFAULT now() ON UPDATE now(),
    
    -- 设置主键
    PRIMARY KEY(id)
    
-- 存储引擎和字符集
) ENGINE=INNODB  -- INNODB支持事务,MyISAM不支持
  DEFAULT CHARACTER SET=utf8mb4
  COMMENT='用户表';  -- 表注释

-- ==================== 日志表 ====================
DROP TABLE IF EXISTS log_info;

CREATE TABLE log_info(
    id INT PRIMARY KEY auto_increment,  -- 主键,简写形式
    
    user_name VARCHAR(128) NOT NULL,
    
    op VARCHAR(256) NOT NULL COMMENT '操作描述',  -- 字段注释
    
    create_time DATETIME DEFAULT now(),
    update_time DATETIME DEFAULT now() ON UPDATE now()
    
) DEFAULT charset='utf8mb4';

五、总结与代码对应关系

5.1 Spring事务实现方式对比

java 复制代码
// 方式一:编程式事务(手动控制)
public void programmatic() {
    // 代码体现:手动getTransaction、commit、rollback
    TransactionStatus status = manager.getTransaction(definition);
    try {
        // 业务代码
        manager.commit(status);
    } catch(Exception e) {
        manager.rollback(status);
        throw e;
    }
}

// 方式二:声明式事务(注解控制)
@Transactional  // 代码体现:一个注解搞定所有事务管理
public void declarative() {
    // 业务代码
    // Spring自动处理开启、提交、回滚
}

5.2 @Transactional关键属性代码体现

java 复制代码
@Transactional(
    // 1. rollbackFor:异常回滚
    rollbackFor = Exception.class,  // 代码:指定IOException等检查型异常也回滚
    
    // 2. isolation:隔离级别
    isolation = Isolation.READ_COMMITTED,  // 代码:避免脏读
    
    // 3. propagation:传播机制
    propagation = Propagation.REQUIRED  // 代码:决定事务如何在方法间传播
)
public void method() {
    // 业务代码
}

5.3 事务传播机制总结表

java 复制代码
// 代码体现:在Service方法上添加不同注解

// REQUIRED(默认)
@Transactional(propagation = Propagation.REQUIRED)
public void requiredMethod() {
    // 加入当前事务,一损俱损
}

// REQUIRES_NEW(独立事务)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewMethod() {
    // 新建事务,挂起调用者事务
}

// NESTED(嵌套事务)
@Transactional(propagation = Propagation.NESTED)
public void nestedMethod() {
    // 创建嵌套事务,可实现局部回滚
    try {
        // 业务代码
    } catch(Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        // 只回滚嵌套事务
    }
}

// NEVER(不支持事务)
@Transactional(propagation = Propagation.NEVER)
public void neverMethod() {
    // 调用者有事务就抛异常
}

六、常见问题与代码解答

Q1:为什么要在Service层而不是Controller层使用事务?

代码说明

java 复制代码
// ❌ 错误:在Controller层放事务
@RestController
public class UserController {
    @Transactional
    public String register() {
        // Controller应该只负责请求和响应
        // 如果业务逻辑复杂(调用多个Service),事务范围会过大
        userService.insertUser();  // 操作1
        orderService.createOrder(); // 操作2
        logService.recordLog();    // 操作3
        // 事务范围包含所有操作,性能差
    }
}

// ✅ 正确:在Service层放事务
@Service
public class UserService {
    @Transactional
    public void registerUser() {
        // Service层代表业务逻辑
        // 事务范围只包含当前业务
        userMapper.insert();
        logMapper.insert();  // 相关业务在同一个事务
    }
}

结论代码联系@Transactional放在Service层,事务边界更清晰,符合单一职责原则。

Q2:NESTED和REQUIRED有什么区别?

代码对比

java 复制代码
// ========== REQUIRED(全部回滚) ==========
@Transactional(propagation = Propagation.REQUIRED)
public void parent() {
    child();  // REQUIRED,加入当前事务
}

@Transactional(propagation = Propagation.REQUIRED)
public void child() {
    // 抛异常 → 整个事务(parent+child)回滚
    throw new RuntimeException();
}

// ========== NESTED(局部回滚) ==========
@Transactional(propagation = Propagation.REQUIRED)
public void parent() {
    child();  // NESTED,创建嵌套事务
}

@Transactional(propagation = Propagation.NESTED)
public void child() {
    try {
        throw new RuntimeException();
    } catch(Exception e) {
        // 只回滚嵌套事务,parent继续
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

结论代码联系Propagation.NESTED + setRollbackOnly() 实现了局部回滚,而REQUIRED会回滚整个事务。

Q3:如何在事务中捕获异常并实现部分回滚?

答案代码

java 复制代码
/**
 * 答案:使用NESTED传播机制 + 手动setRollbackOnly()
 */
@Transactional(propagation = Propagation.NESTED)  // 关键1:必须是NESTED
public void partialRollback() {
    try {
        // 可能失败的操作
        riskOperation();
    } catch(Exception e) {
        // 关键2:手动回滚嵌套事务
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        
        // 记录日志或进行补偿操作
        log.error("操作失败,但主事务继续", e);
    }
    
    // 主业务逻辑继续执行
    mainBusiness();
    // 结果:riskOperation()回滚,mainBusiness()提交
}

七、扩展知识点

7.1 事务ACID特性的代码体现

java 复制代码
@Transactional
public void demonstrateACID() {
    // 原子性(Atomicity)
    // 代码体现:try块中的所有操作要么全部commit,要么全部rollback
    try {
        insertUser();    // 操作1
        insertOrder();   // 操作2
        updateStock();   // 操作3
        // 全部成功 → commit()
    } catch(Exception e) {
        // 任何失败 → rollback()
    }
    
    // 一致性(Consistency)
    // 代码体现:事务开始前和结束后,数据库约束不被破坏
    
    // 隔离性(Isolation)
    // 代码体现:@Transactional(isolation = Isolation.READ_COMMITTED)
    
    // 持久性(Durability)
    // 代码体现:commit()后,数据永久保存,即使系统崩溃
}

7.2 事务实现原理(AOP代理)

java 复制代码
// Spring AOP代理伪代码(帮助你理解@Transactional如何工作)
public class UserServiceProxy extends UserService {
    private UserService target;  // 真实对象
    private TransactionManager txManager;  // 事务管理器
    
    @Override
    public void registryUser(String name, String password) {
        // 1. 解析@Transactional注解
        TransactionDefinition definition = parseTransactionAnnotation();
        
        // 2. 开启事务(AOP前置通知)
        TransactionStatus status = txManager.getTransaction(definition);
        
        try {
            // 3. 调用真实对象的方法
            target.registryUser(name, password);
            
            // 4. 提交事务(AOP返回通知)
            txManager.commit(status);
        } catch(RuntimeException e) {
            // 5. 回滚事务(AOP异常通知)
            txManager.rollback(status);
            throw e;
        }
    }
}

八、最佳实践代码模板

java 复制代码
/**
 * Service层事务最佳实践模板
 */
@Service
public class BestPracticeService {

    /**
     * 1. 默认使用REQUIRED
     * 适用场景:大多数业务操作
     */
    @Transactional  // 不指定propagation,默认就是REQUIRED
    public void standardBusiness() {
        // 多个数据库操作
    }

    /**
     * 2. 需要独立事务使用REQUIRES_NEW
     * 适用场景:发送邮件、记录审计日志等不影响主业务的操作
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void independentOperation() {
        // 即使失败也不影响主事务
    }

    /**
     * 3. 需要局部回滚使用NESTED
     * 适用场景:主订单必须成功,但订单明细可以失败
     */
    @Transactional(propagation = Propagation.NESTED)
    public void nestedOperation() {
        try {
            // 可能失败的操作
        } catch(Exception e) {
            // 局部回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }

    /**
     * 4. 只读查询使用readOnly = true
     * 作用:优化性能,数据库可能启用只读优化
     */
    @Transactional(readOnly = true)
    public UserInfo getUserInfo(Integer userId) {
        // 只读查询
        return userMapper.selectById(userId);
    }

    /**
     * 5. 指定超时时间
     * 作用:防止长时间占用数据库连接
     */
    @Transactional(timeout = 30)  // 30秒超时
    public void longTimeOperation() {
        // 可能耗时的操作
    }
}

最后的话

记住这个核心原则

html 复制代码
// 看到代码时,问自己三个问题:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
public void method() {
    // 1. 当前有事务吗?(传播机制决定)
    // 2. 能看到其他事务未提交的数据吗?(隔离级别决定)
    // 3. 抛 checked exception会回滚吗?(rollbackFor决定)
}

希望这份带详细注释的指南能帮你彻底理解Spring事务!每个代码片段都与知识点直接挂钩,注释覆盖了从import到方法返回的每一个细节。

八、Spring事务失效场景详解(新手必看)

本章目标:掌握Spring事务的8种常见失效场景,避免在实际开发中踩坑。这是从"会用"到"用好"的关键一步!

8.1 同类自调用问题(最坑!)

问题描述

在同一个类中,非事务方法调用本类的事务方法,事务会完全失效 。这是新手最容易踩、最难排查的坑。

错误代码示例
java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 用户服务类 - 演示同类自调用导致事务失效
 */
@Slf4j
@Service
public class UserService {

    /**
     * 外部调用的入口方法(无事务)
     * 问题:这个方法本身没有@Transactional,它直接调用内部方法
     */
    public void registerUser(String name, String password) {
        log.info("开始注册用户");
        
        // 【致命错误】直接调用本类的方法,使用this调用
        // this指向的是目标对象本身,而不是Spring的代理对象
        // 结果就是绕过了Spring AOP代理,@Transactional注解被完全忽略!
        this.insertUser(name, password);  // 事务失效!
        
        log.info("注册用户结束");
    }

    /**
     * 内部事务方法(带@Transactional)
     * 问题:虽然加了事务注解,但同类调用时不会生效
     */
    @Transactional
    public void insertUser(String name, String password) {
        log.info("插入用户数据到数据库");
        userInfoMapper.insert(name, password);
        
        // 模拟异常
        int i = 10 / 0;  // 抛出RuntimeException
        
        // 期望:事务回滚,数据库没有数据
        // 实际:事务失效,数据库有数据(操作未回滚)
    }
}
失效原因分析
java 复制代码
// Spring AOP代理工作原理(伪代码)
public class UserService$$Proxy extends UserService {
    private UserService target;  // 真实的目标对象
    private TransactionInterceptor interceptor;  // 事务拦截器
    
    @Override
    public void insertUser(String name, String password) {
        // 代理对象会在这里开启事务
        TransactionStatus status = txManager.getTransaction(definition);
        try {
            target.insertUser(name, password);  // 调用真实对象的方法
            txManager.commit(status);
        } catch(Exception e) {
            txManager.rollback(status);
            throw e;
        }
    }
}

// 当同类自调用时:
public void registerUser() {
    // this指向的是UserService(目标对象),不是UserService$$Proxy(代理对象)
    // 绕过了AOP代理,直接调用目标方法,事务拦截器完全不生效
    this.insertUser();  // 没有AOP代理介入
}
正确解决方案
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;  // 注入自身代理对象
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 正确做法:注入自己的代理对象,通过代理对象调用
 */
@Slf4j
@Service
public class UserService {

    // 【解决方案1】注入Spring容器中的代理对象
    // Spring会注入一个代理后的UserService实例
    @Autowired
    private UserService selfProxy;  // 自己代理自己!

    public void registerUser(String name, String password) {
        log.info("开始注册用户");
        
        // 通过代理对象调用,AOP生效!
        // Spring会拦截这次调用,执行事务逻辑
        selfProxy.insertUser(name, password);  // ✅ 事务有效!
        
        log.info("注册用户结束");
    }

    @Transactional
    public void insertUser(String name, String password) {
        userInfoMapper.insert(name, password);
        int i = 10 / 0;  // 异常后事务正确回滚
    }

    // 【解决方案2】将方法拆分到不同Service
    // UserService调用LogService,天然跨类调用,AOP永远生效
    @Autowired
    private LogService logService;  // 注入其他Service
    
    public void registerUser2(String name, String password) {
        this.insertUser(name, password);  // 本类方法(无事务)
        logService.recordLog(name);       // 其他类方法(有事务),AOP生效
    }
}

8.2 方法修饰符非public

问题描述

Spring AOP基于代理实现,private、protected、package-private 方法上的@Transactional不会生效。

错误代码示例
java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 演示非public方法事务失效
 */
@Slf4j
@Service
public class OrderService {

    /**
     * 公共方法调用私有事务方法
     */
    public void createOrder(Order order) {
        // 调用私有方法
        this.saveOrder(order);  // 事务失效!
    }

    /**
     * 【错误】私有方法上的事务注解
     * 原因:Spring AOP代理无法拦截私有方法
     * JDK动态代理:基于接口,只能代理public方法
     * CGLIB代理:虽然可以代理类,但默认不拦截非public方法
     */
    @Transactional
    private void saveOrder(Order order) {
        orderMapper.insert(order);  // 数据库操作
        int i = 10 / 0;  // 期望回滚,但不会回滚
    }

    /**
     * 【错误】protected方法上的事务注解
     */
    @Transactional
    protected void updateOrder(Order order) {
        orderMapper.update(order);
    }

    /**
     * 【错误】package-private方法上的事务注解
     */
    @Transactional
    void deleteOrder(Integer orderId) {
        orderMapper.delete(orderId);
    }
}
正确解决方案
java 复制代码
@Slf4j
@Service
public class OrderService {

    /**
     * 【正确】将事务注解放在public方法上
     * Spring AOP代理可以正常拦截public方法
     */
    @Transactional
    public void createOrder(Order order) {
        // 所有数据库操作都在同一个事务中
        orderMapper.insert(order);
        orderDetailMapper.insert(order.getItems());
        int i = 10 / 0;  // 异常后正确回滚
    }

    /**
     * 【正确】提取到独立的Service类
     * 跨类调用,即使是package-private也生效(因为走代理)
     */
    @Service
    public class OrderInnerService {
        @Transactional
        public void saveOrder(Order order) {
            orderMapper.insert(order);
        }
    }
}

8.3 方法是final/static

问题描述

final方法不能被重写,CGLIB无法生成代理子类;static方法属于类,不属于实例,无法被代理。

错误代码示例
java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 演示final/static方法事务失效
 */
@Slf4j
@Service
public class ProductService {

    /**
     * 【错误】final方法上的事务注解
     * 原因:CGLIB代理通过继承类生成代理子类,无法重写final方法
     */
    @Transactional
    public final void addProduct(Product product) {
        productMapper.insert(product);  // 事务失效!
        int i = 10 / 0;
    }

    /**
     * 【错误】static方法上的事务注解
     * 原因:static方法属于类,代理对象无法拦截
     */
    @Transactional
    public static void updateProduct(Product product) {
        // 静态方法无法在Spring容器中管理
        // 也就无法创建代理
    }
}
正确解决方案
java 复制代码
@Slf4j
@Service
public class ProductService {

    /**
     * 【正确】去掉final修饰符
     * CGLIB可以正常生成代理子类并重写此方法
     */
    @Transactional
    public void addProduct(Product product) {
        productMapper.insert(product);
    }

    /**
     * 【正确】static方法不要加事务
     * 如果需要事务,改为实例方法
     */
    @Transactional
    public void updateProduct(Product product) {
        productMapper.update(product);
    }
}

8.4 多线程调用问题

问题描述

Spring事务基于ThreadLocal实现,绑定在当前线程。新线程无法获取主线程的事务。

错误代码示例
java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 演示多线程导致事务失效
 */
@Slf4j
@Service
public class MultiThreadService {

    // 线程池
    private ExecutorService executor = Executors.newFixedThreadPool(5);

    @Transactional
    public void createOrderWithAsyncLog(Order order) {
        // 主线程中插入订单(在主事务中)
        orderMapper.insert(order);
        log.info("订单插入成功,主线程事务ID:{}", 
            TransactionSynchronizationManager.getCurrentTransactionName());
        
        // 【致命错误】在新线程中记录日志
        // 新线程无法获取主线程的事务,会创建新连接
        executor.submit(() -> {
            log.info("新线程开始记录日志,事务ID:{}", 
                TransactionSynchronizationManager.getCurrentTransactionName());
            
            // 问题1:这个操作不在主事务中,无法回滚
            logMapper.insert(order.getLog());
            
            // 问题2:如果这里抛出异常,主线程的事务不会感知
            // 问题3:可能导致数据不一致(订单提交了,日志没记录)
            int i = 10 / 0;  // 新线程异常,不影响主事务
        });
        
        // 主线程继续执行
        int j = 10 / 0;  // 主线程异常,订单回滚,但日志可能已在新线程中插入
    }
}
正确解决方案
java 复制代码
import org.springframework.scheduling.annotation.Async;  // 异步注解
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;  // 事务同步
import org.springframework.transaction.support.TransactionSynchronizationManager;  // 事务同步管理器

/**
 * 正确做法:使用事务同步管理器或异步事务
 */
@Slf4j
@Service
public class MultiThreadService {

    /**
     * 方案1:使用TransactionSynchronization(补偿机制)
     * 在事务提交/回滚后执行操作
     */
    @Transactional
    public void createOrderWithSync(Order order) {
        orderMapper.insert(order);
        
        // 注册事务同步回调
        TransactionSynchronizationManager.registerSynchronization(
            new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    // 事务提交成功后,在新线程中执行
                    executor.submit(() -> {
                        logMapper.insert(order.getLog());
                    });
                }
                
                @Override
                public void afterCompletion(int status) {
                    if (status == STATUS_ROLLED_BACK) {
                        log.error("事务回滚,不记录日志");
                    }
                }
            }
        );
    }

    /**
     * 方案2:重构业务逻辑,避免在事务中开新线程
     * 先完成事务,再异步处理
     */
    public void createOrder(Order order) {
        // 1. 先同步完成事务操作
        transactionTemplate.execute(status -> {
            orderMapper.insert(order);
            return true;
        });
        
        // 2. 事务完成后,再异步执行其他操作
        executor.submit(() -> {
            logMapper.insert(order.getLog());
        });
    }

    /**
     * 方案3:使用@Async + 独立事务
     * 注意:这里的@Async方法必须放在不同的类中
     */
    @Service
    public class LogService {
        @Async  // Spring异步执行
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void asyncInsertLog(Log log) {
            // 独立事务,独立线程
            logMapper.insert(log);
        }
    }
}

8.5 异常被catch未抛出

问题描述

事务方法中捕获了异常但没有重新抛出,Spring感知不到异常,不会回滚。

错误代码示例
java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 演示异常被吞导致事务失效
 */
@Slf4j
@Service
public class ExceptionHandleService {

    @Transactional
    public void createOrder(Order order) {
        try {
            orderMapper.insert(order);
            
            // 模拟业务异常
            int i = 10 / 0;  // 抛出ArithmeticException
            
        } catch (Exception e) {
            log.error("创建订单失败:{}", e.getMessage());
            
            // 【致命错误】没有重新抛出异常
            // Spring认为方法正常执行完毕,会提交事务!
            // 订单数据会残留在数据库中(不完整状态)
        }
        
        // 这里还会继续执行,导致逻辑错误
        log.info("订单创建成功");  // 实际上失败了
    }
}
正确解决方案
java 复制代码
@Slf4j
@Service
public class ExceptionHandleService {

    @Transactional
    public void createOrder(Order order) {
        try {
            orderMapper.insert(order);
            int i = 10 / 0;
            
        } catch (Exception e) {
            log.error("创建订单失败:{}", e.getMessage());
            
            // 【正确】重新抛出异常,让Spring感知到
            // Spring捕获异常后会回滚事务
            throw new RuntimeException("订单创建失败", e);  // ✅ 事务回滚
            
            // 或者抛出自定义业务异常
            // throw new BusinessException("ORDER_CREATE_FAILED");
        }
    }

    /**
     * 方案2:如果需要特殊处理,手动标记回滚
     * 场景:异常不能抛出,但又要回滚事务
     */
    @Transactional
    public void createOrder2(Order order) {
        try {
            orderMapper.insert(order);
            int i = 10 / 0;
            
        } catch (Exception e) {
            log.error("创建订单失败:{}", e.getMessage());
            
            // 手动标记事务为只回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            
            // 不抛出异常,但事务仍会回滚
            return;  // 方法正常返回,但事务已标记为回滚
        }
    }
}

8.6 数据库引擎不支持事务

问题描述

MySQL的MyISAM引擎不支持事务,即使代码正确,事务也不会生效。

问题代码(SQL层面)
java 复制代码
-- 创建使用MyISAM引擎的表
CREATE TABLE user_info_myisam (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_name VARCHAR(128)
) ENGINE=MYISAM;  -- MyISAM不支持事务

-- 插入数据
START TRANSACTION;
INSERT INTO user_info_myisam(user_name) VALUES('Tom');
ROLLBACK;  -- 回滚无效!数据已经插入
正确解决方案
java 复制代码
-- 使用InnoDB引擎(支持事务)
CREATE TABLE user_info_innodb (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_name VARCHAR(128)
) ENGINE=INNODB;  -- InnoDB支持事务

-- 现在事务可以正常回滚
START TRANSACTION;
INSERT INTO user_info_innodb(user_name) VALUES('Tom');
ROLLBACK;  -- 数据正确回滚,表中无数据

Spring中的检查代码

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;  // 初始化回调注解

/**
 * 检查数据库引擎是否支持事务
 */
@Component
public class DatabaseEngineChecker {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**
     * @PostConstruct:Spring初始化Bean后执行
     * 作用:在应用启动时检查配置
     */
    @PostConstruct
    public void checkEngine() {
        String sql = "SELECT ENGINE FROM information_schema.TABLES " +
                    "WHERE TABLE_SCHEMA = 'trans_test' " +
                    "AND TABLE_NAME = 'user_info'";
        
        String engine = jdbcTemplate.queryForObject(sql, String.class);
        
        if (!"InnoDB".equalsIgnoreCase(engine)) {
            log.error("警告:user_info表引擎是{},不支持事务!", engine);
            // 可以抛异常阻止应用启动
            throw new IllegalStateException("数据库引擎不支持事务");
        }
        
        log.info("检查通过:数据库引擎支持事务");
    }
}

8.7 未配置事务管理器

问题描述

没有配置DataSourceTransactionManager,Spring无法管理事务。

错误配置(Spring Boot)
java 复制代码
// application.properties中未配置数据源
// spring.datasource.url=... (缺失)
// spring.datasource.username=... (缺失)

// 启动类缺少@EnableTransactionManagement
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
// 结果:@Transactional注解被忽略,事务失效
正确配置
java 复制代码
/**
 * 显式配置事务管理器(Spring Boot通常自动配置,但了解原理很重要)
 */
import org.springframework.context.annotation.Bean;  // Bean定义
import org.springframework.context.annotation.Configuration;  // 配置类
import org.springframework.jdbc.datasource.DataSourceTransactionManager;  // 事务管理器
import org.springframework.transaction.PlatformTransactionManager;  // 事务管理器接口
import org.springframework.transaction.annotation.EnableTransactionManagement;  // 开启事务管理

import javax.sql.DataSource;  // 数据源

/**
 * 事务配置类
 * * @EnableTransactionManagement:启用Spring的注解事务管理
 * 作用:1. 创建事务代理 2. 启用@Transactional注解解析
 * * 在Spring Boot中,只要引入了spring-boot-starter-jdbc,通常自动配置
 * 但显式配置有助于理解原理
 */
@Configuration
@EnableTransactionManagement  // ✅ 开启事务管理
public class TransactionConfig {
    
    /**
     * 定义事务管理器Bean
     * * @param dataSource 数据源(Spring自动注入)
     * @return 事务管理器
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        // DataSourceTransactionManager是JDBC的事务管理器
        // 它管理数据库连接的获取、提交、回滚
        return new DataSourceTransactionManager(dataSource);
    }
}

8.8 传播机制设置错误

问题描述

错误使用NEVER/NOT_SUPPORTED等传播机制,导致事务意外失效或抛出异常。

错误代码示例
java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 演示传播机制误用
 */
@Slf4j
@Service
public class PropagationMisuseService {

    @Transactional
    public void mainBusiness() {
        // 主业务逻辑
        
        // 【致命错误】调用NEVER传播机制的方法
        // NEVER要求当前没有事务,但mainBusiness()有事务
        // 结果:抛出IllegalTransactionStateException异常
        this.noTransactionMethod();
    }

    @Transactional(propagation = Propagation.NEVER)  // 要求无事务环境
    public void noTransactionMethod() {
        log.info("此方法不应在事务中执行");
    }

    // 另一个误用场景
    @Transactional
    public void anotherBusiness() {
        // 【致命错误】调用NOT_SUPPORTED,挂起当前事务
        // 导致主事务被挂起,操作不在事务保护中
        this.notSupportedMethod();
        
        // 后续操作在主事务中,但前面的操作已脱离事务
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void notSupportedMethod() {
        // 此方法以非事务方式运行,当前事务被挂起
        // 如果这里操作数据库失败,不会回滚
        logMapper.insert("日志");  // 不受主事务保护
    }
}
正确解决方案
java 复制代码
@Slf4j
@Service
public class PropagationMisuseService {

    @Transactional
    public void mainBusiness() {
        // 主业务逻辑
        
        // 【正确】如果需要独立事务,使用REQUIRES_NEW
        this.independentMethod();  // 创建新事务,不影响主事务
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void independentMethod() {
        log.info("此方法在独立事务中执行");
        logMapper.insert("日志");
        // 即使这里失败,也只回滚自己的事务
    }

    /**
     * 【正确】明确传播机制的使用场景表
     */
    public class PropagationGuide {
        
        // 场景1:大多数业务 → REQUIRED(默认)
        @Transactional(propagation = Propagation.REQUIRED)
        public void normalBusiness() {
            // 加入当前事务
        }
        
        // 场景2:必须独立 → REQUIRES_NEW
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void mustIndependent() {
            // 审计日志、消息通知等
        }
        
        // 场景3:部分回滚 → NESTED
        @Transactional(propagation = Propagation.NESTED)
        public void partialRollback() {
            // 可选操作,失败不影响主流程
        }
        
        // 场景4:查询 → readOnly = true
        @Transactional(readOnly = true)
        public void query() {
            // 只读查询,优化性能
        }
    }
}

8.9 总结:事务失效检查清单

html 复制代码
/**
 * 在怀疑事务失效时,按此清单检查代码
 */
public class TransactionChecklist {
    
    public void checkBeforeCommit() {
        // □ 1. 方法是否是public?
        //    if (!Modifier.isPublic(method.getModifiers())) return false;
        
        // □ 2. 方法是否是final/static?
        //    if (Modifier.isFinal(method.getModifiers())) return false;
        
        // □ 3. 是否是同类自调用?
        //    if (invocation.getThis() == target) return false;
        
        // □ 4. 异常是否正确抛出?
        //    if (exceptionCaughtButNotRethrown) return false;
        
        // □ 5. 传播机制是否正确?
        //    if (propagation == Propagation.NEVER && hasTransaction) return false;
        
        // □ 6. 数据库引擎是否支持?
        //    if (engine != "InnoDB") return false;
        
        // □ 7. 事务管理器是否配置?
        //    if (txManager == null) return false;
        
        // □ 8. 是否在多线程中调用?
        //    if (Thread.currentThread() != mainThread) return false;
    }
}

终极建议:写完后问自己一句:"我的代码真的能走到Spring的代理逻辑里吗?" 只要记住这一点,80%的事务失效问题都能避免。

相关推荐
-大头.2 小时前
Java泛型实战:类型安全与高效开发
java·开发语言·安全
周杰伦_Jay2 小时前
【操作系统】进程管理与内存管理
java·数据库·缓存
serendipity_hky2 小时前
【SpringCloud | 第3篇】Sentinel 服务保护(限流、熔断降级)
java·后端·spring·spring cloud·微服务·sentinel
Kiri霧2 小时前
Go 切片表达式
java·服务器·golang
漂亮的小碎步丶2 小时前
【2】Spring Boot自动装配
java·spring boot·后端
jimy12 小时前
ps aux|grep pid 和 ps -p pid 的区别
java·linux·开发语言
weixin_437546332 小时前
注释文件夹下脚本的Debug
java·linux·算法
白露与泡影3 小时前
Java关键字解析之final:不可变的本质、设计哲学与并发安全
java·开发语言·安全
Li_7695323 小时前
IDEA 中 maven 图标失踪解决措施
java·maven·intellij-idea