【Spring】事务管理深度解析|从原理到实战

Spring 事务管理是 Spring 框架最核心功能之一,它通过声明式事务编程式事务 两种方式,将开发者从繁琐的 JDBC 事务控制中解放出来,实现 "注解一行,事务全权托管" 的极致简洁

一、核心概念:ACID 与传播机制

1. 事务的 ACID 特性
①.原子性 (Atomicity):事务是最小执行单元,要么全做,要么全不做
②.一致性 (Consistency):事务完成后,数据库状态必须保持一致
③.隔离性 (Isolation):多个事务并发执行时互不影响
④.持久性(Durability):事务提交后,数据永久保存

2. Spring 事务三要素

java 复制代码
// 1. 事务管理器(PlatformTransactionManager)
// 2. 事务属性(TransactionDefinition)
// 3. 事务状态(TransactionStatus)

二、事务管理方式:声明式 vs 编程式

1. 声明式事务(@Transactional 注解)【推荐】

java 复制代码
// 在类或方法上添加注解,Spring 自动创建代理
@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private AccountService accountService;
    
    // 完整配置示例
    @Transactional(
        propagation = Propagation.REQUIRED,      // 传播行为
        isolation = Isolation.READ_COMMITTED,    // 隔离级别
        readOnly = false,                        // 是否只读
        timeout = 30,                            // 超时时间(秒)
        rollbackFor = {InsufficientBalanceException.class}, // 回滚异常
        noRollbackFor = {DataNotFoundException.class}      // 不回滚异常
    )
    public void createOrder(Order order) {
        // 1. 扣减库存
        inventoryService.reduceStock(order.getProductId(), order.getQuantity());
        
        // 2. 扣减余额
        accountService.deductBalance(order.getUserId(), order.getAmount());
        
        // 3. 创建订单
        orderRepository.save(order);
        
        // 4. 发送消息(异常会触发回滚)
        messageService.sendOrderCreatedEvent(order);
        
        // 如果以上任何一步抛出异常,所有操作自动回滚
    }
}

工作原理:

java 复制代码
// Spring 创建代理对象(JDK 动态代理或 CGLIB)
OrderService proxy = new OrderServiceProxy();

// 代理增强逻辑
public void createOrder(Order order) {
    TransactionStatus status = transactionManager.getTransaction(definition);
    try {
        target.createOrder(order); // 执行业务代码
        transactionManager.commit(status); // 成功则提交
    } catch (Exception ex) {
        transactionManager.rollback(status); // 异常则回滚
        throw ex;
    }
}

2. 编程式事务(TransactionTemplate)

适用于事务逻辑复杂、需要细粒度控制的场景:

java 复制代码
@Service
public class TransferService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void transfer(Long fromAccount, Long toAccount, BigDecimal amount) {
        // 编程式控制事务
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    // 执行转账逻辑
                    accountDao.debit(fromAccount, amount);
                    accountDao.credit(toAccount, amount);
                } catch (InsufficientBalanceException ex) {
                    status.setRollbackOnly(); // 手动标记回滚
                }
            }
        });
    }
    
    // 带返回值版本
    public String executeInTransaction() {
        return transactionTemplate.execute(status -> {
            // 业务逻辑
            return "success";
        });
    }
}

更底层的方式(PlatformTransactionManager):

java 复制代码
@Autowired
private PlatformTransactionManager transactionManager;

public void manualTransaction() {
    // 1. 定义事务属性
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    
    // 2. 开启事务
    TransactionStatus status = transactionManager.getTransaction(def);
    
    try {
        // 业务逻辑
        // ...
        
        // 3. 提交
        transactionManager.commit(status);
    } catch (Exception ex) {
        // 4. 回滚
        transactionManager.rollback(status);
        throw ex;
    }
}

三、事务传播行为(Propagation)

传播行为定义:当一个事务方法调用另一个事务方法时,事务如何传播?

java 复制代码
@Transactional(propagation = Propagation.REQUIRED) // 默认
public void methodA() {
    methodB(); // methodB 的事务如何加入?
}

7 种传播行为详解

传播行为 含义 使用场景 示例代码
REQUIRED 默认。当前有事务则加入,无则新建 通用场景 @Transactional(propagation = REQUIRED)
REQUIRES_NEW 挂起当前事务,新建独立事务 审计日志(记录失败) saveAuditLog() 需独立提交
SUPPORTS 当前有事务则加入,无为非事务 查询方法 findUser() 可共用事务
NOT_SUPPORTED 挂起当前事务,以非事务执行 批量查询(加快速度) batchQuery()
MANDATORY 必须有事务,无则抛异常 强制事务环境 内部服务方法
NEVER 必须无事务,有则抛异常 检查类方法 validateData()
NESTED 嵌套事务,可独立回滚 部分回滚场景 saveMain()saveSub() 可单独回

传播行为流程图:

bash 复制代码
调用者事务状态 → 传播行为 → 被调用者事务状态
    无事务    → REQUIRED    → 新建事务
   有事务     → REQUIRED    → 加入事务
    无事务    → REQUIRES_NEW → 新建事务
   有事务     → REQUIRES_NEW → 挂起原事务,新建
    无事务    → NESTED      → 新建事务
   有事务     → NESTED      → 创建 SavePoint(嵌套)

传播行为实战案例
案例 1:REQUIRES_NEW 独立审计日志

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private AuditService auditService;
    
    @Transactional
    public void createOrder(Order order) {
        try {
            // 保存订单
            orderDao.save(order);
            
            // 扣减库存(可能失败)
            inventoryService.reduceStock(order.getProductId());
            
        } finally {
            // 审计日志必须保存,即使下单失败
            auditService.logOrderCreation(order); // REQUIRES_NEW
        }
    }
}

@Service
public class AuditService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOrderCreation(Order order) {
        auditDao.save(new AuditLog(order)); // 独立事务,不受回滚影响
    }
}

案例 2:NESTED 嵌套事务(部分回滚)

java 复制代码
@Service
public class TransferService {
    
    @Transactional
    public void transferWithFee(Long from, Long to, BigDecimal amount) {
        // 主事务:转账
        accountDao.debit(from, amount);
        accountDao.credit(to, amount);
        
        try {
            // 嵌套事务:扣手续费(可独立回滚)
            feeService.deductFee(from, amount.multiply(new BigDecimal("0.01"))); // NESTED
        } catch (Exception e) {
            // 手续费失败不影响主转账
            log.warn("手续费扣取失败", e);
        }
    }
}

@Service
public class FeeService {
    @Transactional(propagation = Propagation.NESTED)
    public void deductFee(Long account, BigDecimal fee) {
        accountDao.debit(account, fee);
        // 如果失败,仅回滚 NESTED 部分,主事务不受影响
    }
}

四、事务隔离级别(Isolation)

隔离级别定义:多个事务并发读写时,如何隔离数据以防止脏读、不可重复读、幻读?

隔离级别 脏读 不可重复读 幻读 使用场景
DEFAULT - - - 默认,使用数据库默认级别(MySQL: RR, Oracle: RC)
READ_UNCOMMITTED ✅ 可能 ✅ 可能 ✅ 可能 极少使用,性能最高,一致性最差
READ_COMMITTED ❌ 不可能 ✅ 可能 ✅ 可能 最常用,读已提交数据(Oracle/SQL Server 默认)
REPEATABLE_READ ❌ 不可能 ❌ 不可能 ✅ 可能 MySQL 默认,保证同一事务多次读一致
SERIALIZABLE ❌ 不可能 ❌ 不可能 ❌ 不可能 最严格,串行执行,性能最低

三种读异常的解释:
1. 脏读(Dirty Read)

java 复制代码
// 事务 A 读取事务 B 未提交的修改
事务 A: SELECT balance = 1000 (脏读)
事务 B: UPDATE balance = 500 (未提交)
事务 A: SELECT balance = 500 (读取到未提交数据)
事务 B: ROLLBACK (回滚)
事务 A: 基于错误的 500 做业务判断 → 数据不一致

2. 不可重复读(Non-repeatable Read)

java 复制代码
// 同一事务内两次读取结果不一致
事务 A: SELECT balance = 1000
事务 B: UPDATE balance = 500 AND COMMIT
事务 A: SELECT balance = 500 (两次读结果不同)

3. 幻读(Phantom Read)

java 复制代码
// 同一事务内两次查询记录数不一致
事务 A: SELECT COUNT(*) FROM users WHERE age > 18 → 10 条
事务 B: INSERT INTO users (age=20) AND COMMIT
事务 A: SELECT COUNT(*) FROM users WHERE age > 18 → 11 条 (幻影行)

隔离级别配置示例

java 复制代码
@Service
public class ReportService {
    
    // 报表查询:允许不可重复读,提高并发性能
    @Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
    public Report generateReport(Date start, Date end) {
        // 多次读取统计数据,允许中间数据变化
        List<Order> orders = orderDao.findByDateRange(start, end);
        BigDecimal total = orderDao.sumAmount(start, end);
        return new Report(orders, total);
    }
    
    // 金融转账:必须可重复读
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void transfer(Long from, Long to, BigDecimal amount) {
        // 查询余额(锁住行,防止其他事务修改)
        Account fromAcc = accountDao.findById(from, LockModeType.PESSIMISTIC_READ);
        
        // 再次确认余额(必须一致)
        if (fromAcc.getBalance().compareTo(amount) < 0) {
            throw new InsufficientBalanceException();
        }
        
        // 执行转账
        accountDao.debit(from, amount);
        accountDao.credit(to, amount);
    }
}

五、事务失效的 8 大陷阱

陷阱 1:自调用问题(最常见)

java 复制代码
@Service
public class OrderService {
    
    public void createOrder(Order order) {
        // 直接调用本类方法,不走代理,事务失效!
        this.saveOrder(order); // ❌ 事务不生效
    }
    
    @Transactional
    public void saveOrder(Order order) {
        orderDao.save(order);
        inventoryService.reduceStock(order.getProductId());
    }
}

// ✅ 解决方法:注入自己(代理对象)
@Service
public class OrderService {
    @Autowired
    private OrderService self; // 注入代理对象
    
    public void createOrder(Order order) {
        self.saveOrder(order); // ✅ 走代理,事务生效
    }
}

陷阱 2:访问修饰符非 public

java 复制代码
@Transactional // ❌ 只对 public 方法有效
private void saveOrder(Order order) { ... } // 改为 public

// 正确
@Transactional
public void saveOrder(Order order) { ... }

陷阱 3:异常被捕获

java 复制代码
@Transactional
public void createOrder(Order order) {
    try {
        orderDao.save(order);
        inventoryService.reduceStock(order.getProductId()); // 抛出异常
    } catch (Exception e) {
        log.error(e); // ❌ 吃掉异常,事务不 rollback
    }
}

// ✅ 正确:抛出异常或手动回滚
@Transactional
public void createOrder(Order order) throws InsufficientStockException {
    orderDao.save(order);
    inventoryService.reduceStock(order.getProductId()); // 抛出异常
}

陷阱 4:检查型异常不回滚

java 复制代码
@Transactional
public void createOrder(Order order) throws IOException { // IOException 是检查型异常
    orderDao.save(order);
    fileService.writeLog(order); // 抛出 IOException
} // ❌ 默认不回滚

// ✅ 解决方案1:指定 rollbackFor
@Transactional(rollbackFor = Exception.class)

// ✅ 解决方案2:抛出运行时异常
throw new RuntimeException("IO error", e);

默认回滚规则:

①.自动回滚 :RuntimeException 及其子类、Error
②.不回滚:CheckedException(如 IOException、SQLException)

陷阱 5:多数据源未指定事务管理器

java 复制代码
// 配置两个数据源
@Bean
public PlatformTransactionManager orderTxManager() {
    return new DataSourceTransactionManager(orderDataSource());
}

@Bean
public PlatformTransactionManager accountTxManager() {
    return new DataSourceTransactionManager(accountDataSource());
}

// 使用
@Transactional(transactionManager = "orderTxManager") // ✅ 必须指定
public void createOrder() { ... }

陷阱 6:异步线程中事务失效

java 复制代码
@Transactional
public void createOrder(Order order) {
    orderDao.save(order);
    
    // ❌ 在新线程中执行,事务上下文丢失
    new Thread(() -> {
        notificationService.sendSms(order); // 事务失效
    }).start();
}

// ✅ 使用 @Async(需配置事务传播)
@Transactional
public void createOrder(Order order) {
    orderDao.save(order);
    asyncNotificationService.sendSms(order); // 独立事务
}

@Service
public class AsyncNotificationService {
    @Async
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendSms(Order order) { ... }
}

陷阱 7:事务超时设置不当

java 复制代码
@Transactional(timeout = 5) // 5 秒超时
public void batchProcess(List<Data> dataList) {
    for (Data data : dataList) {
        process(data); // 处理 10000 条数据,耗时 10 秒
        // ❌ 超时回滚,但可能已经部分提交
    }
}

// ✅ 批量分片
@Transactional(timeout = 30) // 给足够时间
public void batchProcess(List<Data> dataList) {
    for (int i = 0; i < dataList.size(); i += 100) {
        List<Data> batch = dataList.subList(i, Math.min(i + 100, dataList.size()));
        processBatch(batch); // 每批 100 条,快速完成
    }
}

陷阱 8:只读事务误用

java 复制代码
@Transactional(readOnly = true) // 标记只读
public void createOrder(Order order) {
    orderDao.save(order); // ❌ 只读事务中执行写操作,可能不生效
}

// ✅ 读写分离
@Transactional(readOnly = true)
public Order getOrder(Long id) { // 查询用只读
    return orderDao.findById(id);
}

@Transactional // 写操作不用 readOnly
public void createOrder(Order order) { // 创建用读写
    orderDao.save(order);
}

只读事务优势:

①.数据库可优化为只读连接

②.MySQL 下不会创建 read view,减少 MVCC 开销

③.性能提升:查询速度提升 20-30%

六、最佳实践总结

1. 默认配置(90% 场景)

java 复制代码
@Transactional // 全部默认即可
public void businessMethod() { ... }

2. 只读查询优化

java 复制代码
@Transactional(readOnly = true) // 查询专用
public List<User> searchUsers() { ... }

3. 独立审计日志

java 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW) // 独立提交
public void auditLog() { ... }

4. 超时与回滚精准控制

java 复制代码
@Transactional(
    timeout = 10,
    rollbackFor = BusinessException.class,
    noRollbackFor = DataValidationException.class
)
public void preciseControl() { ... }

5. 事务方法入口原则

java 复制代码
// 所有事务从 Service 层入口方法开始
@Transactional
public void serviceMethod() {
    dao1.update();
    dao2.update(); // 同一事务
    externalService.call(); // 可能开启新事务
}

总结

Spring 事务管理 = 声明式注解 + 传播行为控制 + 隔离级别防护 + AOP 代理增强。掌握事务失效的 8 大陷阱,才能写出健壮的企业级代码

相关推荐
lkbhua莱克瓦242 小时前
Java进阶——IO流
java·开发语言·笔记·学习方法·io流
韩立学长2 小时前
【开题答辩实录分享】以《自选便利店商品分类管理系统》为例进行选题答辩实录分享
java·mysql·web
阿杰同学2 小时前
Java中55种锁,高级面试题,最新面试题
java·开发语言
清晓粼溪2 小时前
SpringCloud01-基础概念
java·开发语言·spring cloud
路边草随风2 小时前
java实现发布flink yarn application模式作业
java·大数据·flink·yarn
华仔啊2 小时前
RabbitMQ 如何保证消息不丢失和不重复消费?掌握这 4 个关键点就够了
java·后端·rabbitmq
编程饭碗2 小时前
【Java循环】
java·服务器·算法
学到头秃的suhian3 小时前
SpringMVC的请求流程
java
不爱吃米饭_3 小时前
OpenFeign的相关问题
java