Spring事务管理完全指南:从零到精通(上)

掌握事务原理 | 💡 精通Spring事务 | 🔥 面试必备知识


📖 前言

💭 「数据一致性是系统的生命线,事务管理是保障」

在实际开发中,数据的一致性至关重要。想象一下银行转账:从A账户扣款后,如果系统崩溃导致B账户未收到款项,这将是灾难性的!事务管理就是为了解决这类问题而存在的。

本文将深入浅出地讲解Spring事务管理的方方面面,从基础概念到实战应用,助你完全掌握这一核心技术。

🎯 学习目标

  • ✅ 深入理解事务的四大特性(ACID)
  • ✅ 掌握Spring事务管理机制
  • ✅ 熟练使用XML和注解配置事务
  • ✅ 理解事务传播行为和隔离级别
  • ✅ 解决常见事务问题
  • ✅ 应对面试高频考点

一、🎯 事务基础概念

1.1 什么是事务?

💡 通俗解释:事务就像一个"全有或全无"的操作包裹,要么所有操作都成功,要么全部失败回滚。

🔍 定义:事务(Transaction)是数据库操作的最小工作单元,是一系列操作的集合,这些操作要么全部成功,要么全部失败。

🎭 生活中的例子

复制代码
场景:银行转账
操作步骤:
1. 检查A账户余额是否充足
2. 从A账户扣除1000元
3. 向B账户增加1000元
4. 记录转账日志

问题:如果第2步成功,但第3步失败(系统崩溃),怎么办?
答案:事务会回滚,让A账户的钱恢复原状!

🌟 为什么需要事务?

  1. 保证数据一致性:避免部分成功导致的数据混乱
  2. 提供隔离性:防止并发操作相互干扰
  3. 确保持久性:成功的操作永久保存
  4. 支持回滚:失败时可以恢复到之前状态

1.2 事务的使用场景

java 复制代码
// 🎯 场景1:电商下单
public void createOrder(Order order) {
    // 1. 扣减库存
    // 2. 创建订单
    // 3. 扣减用户积分
    // 4. 发送消息通知
    // 这些操作必须在一个事务中!
}

// 🎯 场景2:用户注册
public void registerUser(User user) {
    // 1. 创建用户账号
    // 2. 初始化用户钱包
    // 3. 发送欢迎邮件
    // 前两步需要事务保护
}

// 🎯 场景3:批量数据导入
public void batchImport(List<Data> dataList) {
    // 1. 验证数据
    // 2. 批量插入
    // 3. 更新统计信息
    // 要么全部成功,要么全部失败
}

二、🏛️ 事务的四大特性(ACID)

💡 记忆技巧 :ACID = A tomicity, C onsistency, I solation, Durability

事务的四大特性是面试必考点,也是理解事务的基础!

2.1 原子性(Atomicity)⚛️

🔍 定义:事务是一个不可分割的工作单位,事务中的操作要么全部完成,要么全部不完成。

🎭 通俗解释:就像化学中的原子,不能再分割!

java 复制代码
/**
 * 原子性示例:转账操作
 * 要么两个操作都成功,要么都失败
 */
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    // 第1步:扣款
    accountMapper.deduct(fromId, amount);
    
    // 模拟异常:如果这里出错,第1步也会回滚
    if (amount.compareTo(new BigDecimal(10000)) > 0) {
        throw new RuntimeException("转账金额过大!");
    }
    
    // 第2步:加款
    accountMapper.add(toId, amount);
    
    // 结果:要么fromId-amount且toId+amount,要么都不变
}

📊 原子性保证机制
全部成功
任一失败
开始事务
执行操作
提交事务
回滚事务
操作生效
恢复到初始状态

2.2 一致性(Consistency)🎯

🔍 定义:事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。

🎭 通俗解释:业务规则不会被破坏!比如转账前后,总金额不变。

java 复制代码
/**
 * 一致性示例:库存扣减
 * 确保库存不会变成负数
 */
@Transactional
public void deductStock(Long productId, Integer quantity) {
    // 查询当前库存
    Product product = productMapper.selectById(productId);
    
    // 业务规则检查:库存必须充足
    if (product.getStock() < quantity) {
        throw new BusinessException("库存不足!");
    }
    
    // 扣减库存
    product.setStock(product.getStock() - quantity);
    productMapper.updateById(product);
    
    // 一致性保证:库存永远 >= 0
}

🌟 一致性约束示例

🎯 业务场景 🔐 一致性规则
银行转账 转账前后总金额不变
订单创建 订单金额 = 商品总价
库存管理 库存数量 >= 0
账户余额 余额 >= 0(除非允许透支)

2.3 隔离性(Isolation)🔒

🔍 定义:多个事务并发执行时,一个事务的执行不应影响其他事务的执行。

🎭 通俗解释:就像考试时每个学生的答卷互不影响!

java 复制代码
/**
 * 隔离性示例:并发扣减库存
 * 多个用户同时购买,互不影响
 */
public class StockService {
    
    // ❌ 没有隔离性的问题代码
    public void deductStockWrong(Long productId, Integer quantity) {
        // 线程1读取:库存=10
        Integer stock = getStock(productId);
        
        // 线程2也读取:库存=10(脏读)
        
        // 线程1扣减:10-5=5
        updateStock(productId, stock - quantity);
        
        // 线程2扣减:10-5=5(数据丢失)
        // 实际应该是10-5-5=0
    }
    
    // ✅ 正确的隔离性保护
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void deductStockCorrect(Long productId, Integer quantity) {
        // 使用数据库锁或乐观锁保证隔离
        Product product = productMapper.selectByIdForUpdate(productId);
        product.setStock(product.getStock() - quantity);
        productMapper.updateById(product);
    }
}

📊 隔离级别对比

🔐 隔离级别 🚫 脏读 🚫 不可重复读 🚫 幻读 📊 性能 💡 使用场景
READ_UNCOMMITTED 读未提交 ❌ 可能 ❌ 可能 ❌ 可能 ⭐⭐⭐⭐⭐ 几乎不用
READ_COMMITTED 读已提交 ✅ 避免 ❌ 可能 ❌ 可能 ⭐⭐⭐⭐ Oracle默认
REPEATABLE_READ 可重复读 ✅ 避免 ✅ 避免 ❌ 可能 ⭐⭐⭐ MySQL默认
SERIALIZABLE 串行化 ✅ 避免 ✅ 避免 ✅ 避免 极高要求

🔍 三种并发问题详解

java 复制代码
/**
 * 并发问题演示类
 */
public class ConcurrencyProblemsDemo {
    
    // 1️⃣ 脏读(Dirty Read)
    // 事务A读取了事务B未提交的数据,但事务B最后回滚了
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void dirtyReadExample() {
        // 线程A:读取账户余额 = 1000
        // 线程B:修改余额为500(未提交)
        // 线程A:读取账户余额 = 500(脏读!)
        // 线程B:回滚,余额恢复为1000
        // 线程A使用了错误的数据500
    }
    
    // 2️⃣ 不可重复读(Non-Repeatable Read)
    // 事务A多次读取同一数据,但数据被事务B修改了
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void nonRepeatableReadExample() {
        // 时刻1:读取账户余额 = 1000
        BigDecimal balance1 = accountMapper.getBalance(id);
        
        // 此时另一个事务修改了余额为500并提交
        
        // 时刻2:再次读取账户余额 = 500
        BigDecimal balance2 = accountMapper.getBalance(id);
        
        // 问题:两次读取结果不一致!
    }
    
    // 3️⃣ 幻读(Phantom Read)
    // 事务A查询一个范围的数据,事务B插入了新数据
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void phantomReadExample() {
        // 第1次查询:查到5条记录
        List<User> users1 = userMapper.selectByAge(20);
        
        // 此时另一个事务插入了一条age=20的记录并提交
        
        // 第2次查询:查到6条记录(幻读!)
        List<User> users2 = userMapper.selectByAge(20);
        
        // 问题:出现了"幻影"数据
    }
}

2.4 持久性(Durability)💾

🔍 定义:事务一旦提交,其对数据库的修改是永久性的,即使系统故障也不会丢失。

🎭 通俗解释:就像你保存了Word文档,即使电脑关机重启,文档还在!

java 复制代码
/**
 * 持久性示例:订单支付
 * 一旦支付成功,订单状态永久改变
 */
@Transactional
public void payOrder(Long orderId) {
    // 1. 查询订单
    Order order = orderMapper.selectById(orderId);
    
    // 2. 更新订单状态为已支付
    order.setStatus(OrderStatus.PAID);
    order.setPayTime(new Date());
    orderMapper.updateById(order);
    
    // 3. 事务提交后,即使系统崩溃,订单状态也不会回退到未支付
    // 这就是持久性!
}

🛡️ 持久性保证机制

  1. Write-Ahead Logging (WAL):先写日志再写数据
  2. 事务日志(Redo Log):记录所有修改操作
  3. 检查点机制:定期将内存数据刷新到磁盘
  4. 崩溃恢复:系统重启后根据日志恢复数据

三、🌟 Spring事务管理机制

3.1 Spring事务管理概述

Spring提供了统一的事务管理抽象,支持多种事务管理方式:
Spring事务管理
编程式事务
声明式事务
TransactionTemplate
PlatformTransactionManager
XML配置
注解配置

🌟 为什么使用Spring事务管理?

  1. 统一的API:不用关心底层是JDBC、Hibernate还是JPA
  2. 解耦:事务管理代码与业务代码分离
  3. 简化开发:使用注解即可,无需手动管理事务
  4. 灵活配置:支持多种传播行为和隔离级别

3.2 Spring事务核心组件

java 复制代码
/**
 * Spring事务核心接口
 */

// 1️⃣ PlatformTransactionManager - 事务管理器接口
public interface PlatformTransactionManager {
    // 获取事务
    TransactionStatus getTransaction(TransactionDefinition definition);
    
    // 提交事务
    void commit(TransactionStatus status);
    
    // 回滚事务
    void rollback(TransactionStatus status);
}

// 2️⃣ TransactionDefinition - 事务定义
public interface TransactionDefinition {
    // 获取传播行为
    int getPropagationBehavior();
    
    // 获取隔离级别
    int getIsolationLevel();
    
    // 获取超时时间
    int getTimeout();
    
    // 是否只读
    boolean isReadOnly();
}

// 3️⃣ TransactionStatus - 事务状态
public interface TransactionStatus {
    // 是否是新事务
    boolean isNewTransaction();
    
    // 设置回滚标记
    void setRollbackOnly();
    
    // 是否已回滚
    boolean isRollbackOnly();
    
    // 是否已完成
    boolean isCompleted();
}

📊 常用事务管理器

🔧 事务管理器 🎯 使用场景 📚 依赖技术
DataSourceTransactionManager 使用JDBC或MyBatis DataSource
HibernateTransactionManager 使用Hibernate SessionFactory
JpaTransactionManager 使用JPA EntityManagerFactory
JtaTransactionManager 分布式事务 JTA

四、📝 Spring事务的XML配置方式

4.1 基础配置

💡 适用场景:需要统一配置多个类的事务,或者不想在代码中加注解

xml 复制代码
<!-- applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 1️⃣ 配置数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!-- 2️⃣ 配置事务管理器 -->
    <bean id="transactionManager" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 3️⃣ 配置事务通知(定义事务的属性) -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 
                配置事务属性:
                name: 方法名匹配模式,支持通配符*
                propagation: 传播行为
                isolation: 隔离级别
                timeout: 超时时间(秒)
                read-only: 是否只读
                rollback-for: 遇到哪些异常回滚
            -->
            
            <!-- 查询方法:只读事务 -->
            <tx:method name="get*" read-only="true" propagation="SUPPORTS"/>
            <tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
            <tx:method name="select*" read-only="true" propagation="SUPPORTS"/>
            <tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
            
            <!-- 修改方法:读写事务 -->
            <tx:method name="save*" propagation="REQUIRED" 
                       isolation="DEFAULT" timeout="30"
                       rollback-for="java.lang.Exception"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="add*" propagation="REQUIRED"/>
            
            <!-- 其他方法:默认事务 -->
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- 4️⃣ 配置AOP切面(定义事务作用范围) -->
    <aop:config>
        <!-- 定义切入点:service包下的所有类的所有方法 -->
        <aop:pointcut id="txPointcut" 
                      expression="execution(* com.example.service..*.*(..))"/>
        
        <!-- 关联事务通知和切入点 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>

</beans>

4.2 XML配置详解

🔍 事务属性详细说明

xml 复制代码
<tx:method 
    name="方法名"
    propagation="传播行为"
    isolation="隔离级别"
    timeout="超时时间"
    read-only="是否只读"
    rollback-for="回滚异常类"
    no-rollback-for="不回滚异常类"/>

📊 属性说明表

🔧 属性 💡 说明 📝 示例值
name 方法名模式(支持*通配) save*, update*
propagation 事务传播行为 REQUIRED, REQUIRES_NEW
isolation 隔离级别 DEFAULT, READ_COMMITTED
timeout 超时时间(秒,-1表示永不超时) 30, -1
read-only 是否只读(优化性能) true, false
rollback-for 遇到哪些异常回滚 Exception.class
no-rollback-for 遇到哪些异常不回滚 IOException.class

4.3 实际使用示例

java 复制代码
/**
 * 使用XML配置的Service类
 * 注意:不需要@Transactional注解
 */
@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private AccountMapper accountMapper;
    
    /**
     * 保存用户(自动应用事务)
     * 匹配XML中的 <tx:method name="save*"/>
     */
    public void saveUser(User user) {
        // 1. 保存用户信息
        userMapper.insert(user);
        
        // 2. 初始化用户账户
        Account account = new Account();
        account.setUserId(user.getId());
        account.setBalance(BigDecimal.ZERO);
        accountMapper.insert(account);
        
        // 如果任何步骤失败,整个事务自动回滚
    }
    
    /**
     * 查询用户(只读事务)
     * 匹配XML中的 <tx:method name="get*" read-only="true"/>
     */
    public User getUser(Long id) {
        return userMapper.selectById(id);
    }
}

五、✨ Spring事务的注解配置方式(推荐)

💡 推荐原因:简单、直观、易维护,是现代Spring应用的首选!

5.1 启用注解事务

方式1:XML配置启用

xml 复制代码
<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="...">

    <!-- 配置数据源和事务管理器(同上) -->
    
    <!-- 启用注解驱动的事务管理 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
</beans>

方式2:Java配置启用(Spring Boot常用)

java 复制代码
/**
 * Spring Boot配置类
 */
@Configuration
@EnableTransactionManagement  // 启用注解事务管理
public class TransactionConfig {
    
    /**
     * 配置事务管理器
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

5.2 @Transactional注解详解

🔍 注解位置:可以放在类上或方法上

java 复制代码
/**
 * @Transactional 注解完整示例
 */
@Service
@Transactional  // 类级别:所有public方法都应用此事务配置
public class OrderService {
    
    /**
     * 方法级别的@Transactional会覆盖类级别的配置
     * 
     * @Transactional参数说明:
     * - propagation: 事务传播行为
     * - isolation: 隔离级别
     * - timeout: 超时时间
     * - readOnly: 是否只读
     * - rollbackFor: 遇到哪些异常回滚
     * - noRollbackFor: 遇到哪些异常不回滚
     */
    @Transactional(
        propagation = Propagation.REQUIRED,      // 必须在事务中运行
        isolation = Isolation.REPEATABLE_READ,   // 可重复读隔离级别
        timeout = 30,                            // 30秒超时
        readOnly = false,                        // 可读写
        rollbackFor = Exception.class,           // 遇到任何Exception都回滚
        noRollbackFor = BusinessException.class  // 业务异常不回滚
    )
    public void createOrder(Order order) {
        // 事务开始...
        
        // 1. 扣减库存
        productService.deductStock(order.getProductId(), order.getQuantity());
        
        // 2. 创建订单
        orderMapper.insert(order);
        
        // 3. 扣减用户积分
        userService.deductPoints(order.getUserId(), order.getPoints());
        
        // 事务提交或回滚
    }
    
    /**
     * 只读事务:优化性能
     */
    @Transactional(readOnly = true)
    public Order getOrder(Long id) {
        return orderMapper.selectById(id);
    }
}

5.3 @Transactional注解常见配置

java 复制代码
/**
 * 注解配置最佳实践
 */
@Service
public class TransactionExamples {
    
    // ✅ 推荐:标准的写操作事务
    @Transactional(rollbackFor = Exception.class)
    public void saveData(Data data) {
        // 保存数据
    }
    
    // ✅ 推荐:只读事务(提高性能)
    @Transactional(readOnly = true)
    public List<Data> queryData() {
        // 查询数据
    }
    
    // ✅ 推荐:需要新事务(不受外层事务影响)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String message) {
        // 保存日志,即使外层事务回滚,日志也保存
    }
    
    // ⚠️ 注意:设置超时时间(防止长时间锁定)
    @Transactional(timeout = 10)
    public void batchProcess(List<Data> dataList) {
        // 批量处理,10秒后超时
    }
    
    // ⚠️ 注意:指定特定异常才回滚
    @Transactional(rollbackFor = {SQLException.class, IOException.class})
    public void complexOperation() {
        // 复杂操作
    }
}

六、🔄 事务传播行为详解

💡 核心问题:当一个事务方法调用另一个事务方法时,应该如何处理事务?

6.1 七种传播行为

🔖 传播行为 📝 说明 💡 使用场景
REQUIRED (默认) 有事务就加入,没有就新建 最常用的方式
REQUIRES_NEW 总是新建事务,挂起当前事务 独立的子事务
SUPPORTS 有事务就加入,没有就以非事务方式运行 查询操作
NOT_SUPPORTED 以非事务方式运行,挂起当前事务 不需要事务的操作
MANDATORY 必须在事务中运行,否则抛异常 强制事务约束
NEVER 不能在事务中运行,否则抛异常 禁止事务
NESTED 嵌套事务,外层回滚内层也回滚 部分回滚场景

6.2 REQUIRED传播行为(最常用)

java 复制代码
/**
 * REQUIRED传播行为示例
 * 如果当前有事务就加入,没有就新建
 */
@Service
public class PropagationRequiredDemo {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private LogService logService;
    
    /**
     * 外层方法:创建事务A
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void outerMethod() {
        System.out.println("外层方法开始");
        
        // 保存订单
        Order order = new Order();
        orderMapper.insert(order);
        
        // 调用内层方法
        logService.innerMethod();  // 会加入到事务A中
        
        System.out.println("外层方法结束");
    }
}

@Service
public class LogService {
    
    @Autowired
    private LogMapper logMapper;
    
    /**
     * 内层方法:也是REQUIRED
     * 会加入到外层的事务A中
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void innerMethod() {
        System.out.println("内层方法执行");
        
        // 保存日志
        Log log = new Log();
        logMapper.insert(log);
        
        // 如果这里抛异常,外层和内层的操作都会回滚
    }
}

/**
 * 执行流程:
 * 1. outerMethod开始 -> 创建事务A
 * 2. 插入订单 -> 在事务A中
 * 3. innerMethod开始 -> 加入事务A(不创建新事务)
 * 4. 插入日志 -> 在事务A中
 * 5. innerMethod结束 -> 不提交(因为不是事务创建者)
 * 6. outerMethod结束 -> 提交事务A
 * 
 * 结果:订单和日志在同一个事务中,要么都成功,要么都失败
 */

6.3 REQUIRES_NEW传播行为

java 复制代码
/**
 * REQUIRES_NEW传播行为示例
 * 总是创建新事务,挂起当前事务
 */
@Service
public class PropagationRequiresNewDemo {
    
    @Autowired
    private AccountService accountService;
    
    /**
     * 外层方法:事务A
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        System.out.println("开始转账");
        
        // 1. 扣款(事务A)
        accountMapper.deduct(fromId, amount);
        
        // 2. 记录日志(新事务B)
        accountService.saveTransferLog(fromId, toId, amount);
        
        // 3. 加款(事务A)
        accountMapper.add(toId, amount);
        
        // 模拟异常
        if (amount.compareTo(new BigDecimal(10000)) > 0) {
            throw new RuntimeException("转账金额过大!");
        }
        
        System.out.println("转账完成");
    }
}

@Service
public class AccountService {
    
    @Autowired
    private LogMapper logMapper;
    
    /**
     * REQUIRES_NEW:创建新事务B
     * 即使外层事务A回滚,这里的日志也会保存!
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveTransferLog(Long fromId, Long toId, BigDecimal amount) {
        Log log = new Log();
        log.setContent("从账户" + fromId + "向" + toId + "转账" + amount);
        logMapper.insert(log);
        
        // 这个方法结束时,事务B立即提交
    }
}

/**
 * 执行结果:
 * - 如果转账成功:订单、日志都保存
 * - 如果转账失败:订单回滚,但日志仍然保存!
 * 
 * 应用场景:
 * 1. 操作日志记录(即使业务失败也要记录)
 * 2. 审计追踪
 * 3. 消息发送记录
 */

6.4 NESTED传播行为

java 复制代码
/**
 * NESTED传播行为示例
 * 嵌套事务:外层回滚内层也回滚,但内层回滚不影响外层
 */
@Service
public class PropagationNestedDemo {
    
    @Autowired
    private RewardService rewardService;
    
    /**
     * 外层方法:创建订单
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        // 1. 保存订单(必须成功)
        orderMapper.insert(order);
        
        try {
            // 2. 发放优惠券(可以失败)
            rewardService.giveCoupon(order.getUserId());
        } catch (Exception e) {
            // 捕获异常,不影响订单创建
            log.error("发放优惠券失败", e);
        }
        
        // 3. 发送通知
        notificationService.sendOrderNotification(order);
    }
}

@Service
public class RewardService {
    
    /**
     * NESTED:嵌套事务
     * 如果失败,只回滚优惠券操作,不影响订单
     */
    @Transactional(propagation = Propagation.NESTED)
    public void giveCoupon(Long userId) {
        // 发放优惠券
        couponMapper.insert(new Coupon(userId));
        
        // 如果这里抛异常
        // 1. 只回滚giveCoupon方法的操作
        // 2. 不影响createOrder中的订单保存
        // 3. 前提是外层捕获了异常
    }
}

相关推荐
fengxin_rou2 小时前
【黑马点评实战篇|第一篇:基于Redis实现登录】
java·开发语言·数据库·redis·缓存
我待_JAVA_如初恋2 小时前
Redis常用的数据类型之String
数据库·redis·缓存
@ chen2 小时前
MySQL 中的锁机制
数据库·mysql
Elastic 中国社区官方博客2 小时前
Elasticsearch:使用 Elastic Workflows 构建自动化
大数据·数据库·人工智能·elasticsearch·搜索引擎·自动化·全文检索
数智工坊2 小时前
【数据结构-栈】3.1栈的顺序存储-链式存储
java·开发语言·数据结构
短剑重铸之日2 小时前
《设计模式》第七篇:适配器模式
java·后端·设计模式·适配器模式
OnYoung2 小时前
编写一个Python脚本自动下载壁纸
jvm·数据库·python
Apple_羊先森2 小时前
ORACLE数据库巡检SQL脚本--15、表空间的运行状态
数据库·sql·oracle
DFT计算杂谈2 小时前
VASP+Wannier90 计算位移电流和二次谐波SHG
java·服务器·前端·python·算法