Spring Boot 事务管理完全指南

Spring Boot 事务管理完全指南

一、事务基础概念

1.1 什么是事务?

事务(Transaction) 是数据库操作的基本单位,它是一组要么全部执行成功、要么全部不执行的SQL语句序列。事务确保数据的完整性和一致性。

1.2 ACID 特性 ⭐⭐⭐⭐⭐

事务具有四个核心特性,简称 ACID

A - Atomicity(原子性)
  • 事务中的所有操作要么全部完成,要么全部不完成
  • 不会出现部分执行的情况
  • 如果事务中任何操作失败,整个事务都会回滚
typescript 复制代码
@Transactional
public void transferMoney(String from, String to, BigDecimal amount) {
    // 这两个操作必须同时成功或同时失败
    accountDao.debit(from, amount);   // 扣款
    accountDao.credit(to, amount);    // 入账
}
C - Consistency(一致性)
  • 事务执行前后,数据库必须保持一致性状态
  • 数据必须符合所有预定义的规则(约束、触发器、级联等)
sql 复制代码
-- 假设账户余额不能为负数
ALTER TABLE account ADD CONSTRAINT check_balance CHECK (balance >= 0);
I - Isolation(隔离性)
  • 多个并发事务之间互不干扰
  • 每个事务都感觉不到其他事务在同时执行
  • 通过隔离级别来控制并发访问
D - Durability(持久性)
  • 事务一旦提交,对数据的改变就是永久的
  • 即使系统故障,数据也不会丢失

二、Spring 事务管理架构

2.1 Spring 事务抽象层

Spring 提供了一个统一的事务管理抽象层,屏蔽了不同事务API的差异:

css 复制代码
┌─────────────────────────────────────┐
│     Spring 应用代码                  │
│     @Transactional                   │
├─────────────────────────────────────┤
│  PlatformTransactionManager         │ ← 核心接口
│  (平台事务管理器)                     │
├──────────┬──────────┬───────────────┤
│          │          │               │
│  DataSource  JPA   Hibernate       │
│  Transaction Transaction Transaction│
│  Manager   Manager   Manager        │
└──────────┴──────────┴───────────────┘

2.2 核心接口

PlatformTransactionManager

Spring 事务管理的核心接口:

csharp 复制代码
public interface PlatformTransactionManager {
    // 获取事务状态
    TransactionStatus getTransaction(TransactionDefinition definition);
    
    // 提交事务
    void commit(TransactionStatus status);
    
    // 回滚事务
    void rollback(TransactionStatus status);
}
常用实现类:
  • DataSourceTransactionManager:JDBC 事务管理器
  • JpaTransactionManager:JPA 事务管理器
  • HibernateTransactionManager:Hibernate 事务管理器
  • JtaTransactionManager:分布式事务管理器

三、声明式事务(@Transactional)

3.1 基本使用

启用事务支持
less 复制代码
@SpringBootApplication
@EnableTransactionManagement  // 启用事务管理
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

注意 :Spring Boot 自动配置已默认启用事务管理,通常不需要显式添加 @EnableTransactionManagement


方法级别事务
typescript 复制代码
@Service
public class AccountService {
    
    @Autowired
    private AccountDao accountDao;
    
    /**
     * 转账操作 - 需要事务保证
     */
    @Transactional
    public void transferMoney(String fromAccount, String toAccount, BigDecimal amount) {
        // 1. 从源账户扣款
        accountDao.debit(fromAccount, amount);
        
        // 2. 向目标账户入账
        accountDao.credit(toAccount, amount);
        
        // 如果任何一步失败,整个事务回滚
    }
}

类级别事务
less 复制代码
@Service
@Transactional  // 类上所有 public 方法都启用事务
public class OrderService {
    
    public void createOrder(Order order) {
        // 自动在事务中执行
    }
    
    public void cancelOrder(Long orderId) {
        // 自动在事务中执行
    }
}

3.2 @Transactional 属性详解 ⭐⭐⭐⭐⭐

ini 复制代码
@Transactional(
    // 1. 传播行为
    propagation = Propagation.REQUIRED,
    
    // 2. 隔离级别
    isolation = Isolation.READ_COMMITTED,
    
    // 3. 超时时间(秒)
    timeout = 30,
    
    // 4. 是否只读
    readOnly = false,
    
    // 5. 回滚规则
    rollbackFor = {Exception.class},
    rollbackForClassName = {"RuntimeException"},
    
    // 6. 不回滚规则
    noRollbackFor = {BusinessException.class},
    noRollbackForClassName = {"CustomException"}
)
public void businessMethod() {
    // 业务逻辑
}

3.3 事务传播行为(Propagation)⭐⭐⭐⭐⭐

传播行为定义了当事务方法被另一个事务方法调用时,事务应该如何传播。

7 种传播行为:
传播行为 说明 使用场景
REQUIRED 如果存在事务则加入,否则新建事务(默认) 大多数场景
REQUIRES_NEW 总是新建事务,挂起当前事务 独立记录日志、审计
SUPPORTS 如果存在事务则加入,否则非事务执行 查询操作
NOT_SUPPORTED 总是非事务执行,挂起当前事务 批量处理、性能敏感操作
MANDATORY 必须在事务中执行,否则抛异常 强制要求事务的场景
NEVER 必须非事务执行,否则抛异常 不允许事务的场景
NESTED 嵌套事务,保存点机制 部分回滚场景

示例 1:REQUIRED(默认)
java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private PaymentService paymentService;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        // 创建订单
        orderDao.insert(order);
        
        // 调用支付服务 - 加入当前事务
        paymentService.processPayment(order);
        
        // 如果这里抛出异常,订单和支付都会回滚
    }
}

@Service
public class PaymentService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void processPayment(Order order) {
        // 处理支付 - 加入 createOrder 的事务
        paymentDao.insert(order.getPayment());
    }
}

执行流程:

scss 复制代码
createOrder 开始事务
  ├─ orderDao.insert()
  ├─ processPayment() [加入同一事务]
  │   └─ paymentDao.insert()
  └─ 如果任何地方失败 → 全部回滚

示例 2:REQUIRES_NEW
java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private AuditLogService auditLogService;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        // 创建订单
        orderDao.insert(order);
        
        try {
            // 记录审计日志 - 独立事务
            auditLogService.logOrderCreation(order);
        } catch (Exception e) {
            // 日志记录失败不影响订单创建
            log.error("Audit log failed", e);
        }
        
        // 继续其他业务逻辑
    }
}

@Service
public class AuditLogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOrderCreation(Order order) {
        // 新建独立事务,不受外层事务影响
        auditLogDao.insert(order);
        
        // 即使这个方法失败并回滚,也不影响外层事务
    }
}

执行流程:

lua 复制代码
createOrder 开始事务 T1
  ├─ orderDao.insert()
  ├─ suspend T1
  ├─ logOrderCreation 开始新事务 T2
  │   ├─ auditLogDao.insert()
  │   └─ T2 提交或回滚(独立)
  ├─ resume T1
  └─ T1 提交或回滚

示例 3:NESTED(嵌套事务)
java 复制代码
@Service
public class BatchService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void batchProcess(List<Order> orders) {
        for (Order order : orders) {
            try {
                // 每个订单作为嵌套事务
                processSingleOrder(order);
            } catch (Exception e) {
                // 单个订单失败,回滚到保存点,不影响其他订单
                log.error("Order {} failed", order.getId(), e);
            }
        }
    }
    
    @Transactional(propagation = Propagation.NESTED)
    public void processSingleOrder(Order order) {
        // 嵌套事务 - 设置保存点
        orderDao.insert(order);
        paymentDao.insert(order.getPayment());
        
        // 如果失败,只回滚到这个保存点
    }
}

执行流程:

scss 复制代码
batchProcess 开始事务 T1
  ├─ processSingleOrder(order1) [保存点 SP1]
  │   ├─ 成功 → 释放 SP1
  │   └─ 失败 → 回滚到 SP1
  ├─ processSingleOrder(order2) [保存点 SP2]
  │   ├─ 成功 → 释放 SP2
  │   └─ 失败 → 回滚到 SP2
  └─ T1 提交(成功的订单保留)

注意 :NESTED 仅支持 DataSourceTransactionManager,且需要 JDBC 3.0+ 支持保存点


3.4 事务隔离级别(Isolation)⭐⭐⭐⭐

隔离级别定义了事务之间的可见性程度,用于解决并发问题。

并发问题:
问题 描述 示例
脏读(Dirty Read) 读取到其他事务未提交的数据 T1 修改数据但未提交,T2 读取到修改后的数据
不可重复读(Non-repeatable Read) 同一事务中多次读取结果不一致 T1 两次读取之间,T2 修改并提交了数据
幻读(Phantom Read) 同一事务中查询返回的行数不一致 T1 两次查询之间,T2 插入或删除了数据

5 种隔离级别:
隔离级别 脏读 不可重复读 幻读 性能
READ_UNCOMMITTED ❌ 可能 ❌ 可能 ❌ 可能 最快
READ_COMMITTED ✅ 避免 ❌ 可能 ❌ 可能
REPEATABLE_READ ✅ 避免 ✅ 避免 ❌ 可能(MySQL避免) 中等
SERIALIZABLE ✅ 避免 ✅ 避免 ✅ 避免 最慢
DEFAULT 使用数据库默认 - - -

示例:
java 复制代码
@Service
public class ProductService {
    
    /**
     * 高并发场景 - 使用 READ_COMMITTED
     * 允许不可重复读,提高并发性能
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public Product getProduct(Long id) {
        return productDao.findById(id);
    }
    
    /**
     * 财务计算 - 使用 REPEATABLE_READ
     * 确保多次读取结果一致
     */
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public BigDecimal calculateTotal(List<Long> orderIds) {
        BigDecimal total = BigDecimal.ZERO;
        for (Long orderId : orderIds) {
            // 多次读取,确保数据一致性
            Order order = orderDao.findById(orderId);
            total = total.add(order.getAmount());
        }
        return total;
    }
    
    /**
     * 库存扣减 - 使用 SERIALIZABLE
     * 最高隔离级别,避免超卖
     */
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void deductStock(Long productId, int quantity) {
        Product product = productDao.findById(productId);
        if (product.getStock() < quantity) {
            throw new BusinessException("库存不足");
        }
        product.setStock(product.getStock() - quantity);
        productDao.update(product);
    }
}

3.5 事务超时和只读优化

超时设置
typescript 复制代码
@Service
public class ReportService {
    
    /**
     * 设置超时时间为 60 秒
     * 如果事务执行超过 60 秒,自动回滚
     */
    @Transactional(timeout = 60)
    public List<Order> generateReport(Date startDate, Date endDate) {
        // 复杂查询和数据处理
        return orderDao.findOrdersBetween(startDate, endDate);
    }
}

只读事务优化
kotlin 复制代码
@Service
public class QueryService {
    
    /**
     * 只读事务 - 性能优化
     * 1. 数据库可以优化查询执行计划
     * 2. 某些数据库会跳过锁检查
     * 3. 明确语义,防止意外修改
     */
    @Transactional(readOnly = true)
    public List<User> findAllUsers() {
        return userDao.findAll();
    }
    
    @Transactional(readOnly = true)
    public User findUserById(Long id) {
        return userDao.findById(id);
    }
}

3.6 回滚规则 ⭐⭐⭐⭐

默认回滚规则
  • 运行时异常(RuntimeException) :自动回滚
  • 受检异常(Checked Exception) :不回滚
typescript 复制代码
@Service
public class AccountService {
    
    @Transactional
    public void transfer(String from, String to, BigDecimal amount) {
        accountDao.debit(from, amount);
        
        // 运行时异常 - 自动回滚 ✅
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("金额必须大于0");
        }
        
        accountDao.credit(to, amount);
    }
}

自定义回滚规则
csharp 复制代码
@Service
public class OrderService {
    
    /**
     * 指定回滚异常
     */
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Order order) throws Exception {
        // 任何 Exception 及其子类都会回滚
        orderDao.insert(order);
        
        // 即使是受检异常也会回滚
        if (order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new Exception("订单金额无效");
        }
    }
    
    /**
     * 指定不回滚异常
     */
    @Transactional(noRollbackFor = BusinessException.class)
    public void updateOrder(Order order) {
        orderDao.update(order);
        
        // 业务异常不会导致回滚
        if (someCondition) {
            throw new BusinessException("业务规则校验失败");
        }
    }
    
    /**
     * 组合使用
     */
    @Transactional(
        rollbackFor = {SQLException.class, IOException.class},
        noRollbackFor = {ValidationException.class}
    )
    public void complexOperation() throws SQLException, IOException {
        // SQLException 和 IOException 会回滚
        // ValidationException 不会回滚
    }
}

四、事务失效场景与解决方案 ⭐⭐⭐⭐⭐

4.1 常见失效场景

❌ 场景 1:方法不是 public
less 复制代码
@Service
public class AccountService {
    
    @Transactional
    private void transfer() {  // ❌ 无效 - 必须是 public
        // ...
    }
    
    @Transactional
    protected void update() {  // ❌ 无效 - 必须是 public
        // ...
    }
}

✅ 解决方案:

typescript 复制代码
@Transactional
public void transfer() {  // ✅ 正确
    // ...
}

❌ 场景 2:自调用(Self-invocation)
typescript 复制代码
@Service
public class AccountService {
    
    public void methodA() {
        this.methodB();  // ❌ 自调用,事务无效
    }
    
    @Transactional
    public void methodB() {
        // 事务不会生效
    }
}

原因:Spring AOP 基于代理,自调用绕过了代理对象

✅ 解决方案 1:注入自身

less 复制代码
@Service
public class AccountService {
    
    @Autowired
    @Lazy  // 避免循环依赖
    private AccountService self;
    
    public void methodA() {
        self.methodB();  // ✅ 通过代理调用
    }
    
    @Transactional
    public void methodB() {
        // 事务生效
    }
}

✅ 解决方案 2:提取到另一个 Service

typescript 复制代码
@Service
public class AccountService {
    
    @Autowired
    private TransferService transferService;
    
    public void methodA() {
        transferService.methodB();  // ✅ 跨 Service 调用
    }
}

@Service
public class TransferService {
    
    @Transactional
    public void methodB() {
        // 事务生效
    }
}

✅ 解决方案 3:使用 AopContext

typescript 复制代码
@Service
public class AccountService {
    
    public void methodA() {
        ((AccountService) AopContext.currentProxy()).methodB();  // ✅
    }
    
    @Transactional
    public void methodB() {
        // 事务生效
    }
}

注意 :需要在启动类添加 @EnableAspectJAutoProxy(exposeProxy = true)


❌ 场景 3:异常被捕获
typescript 复制代码
@Service
public class AccountService {
    
    @Transactional
    public void transfer(String from, String to, BigDecimal amount) {
        try {
            accountDao.debit(from, amount);
            accountDao.credit(to, amount);
        } catch (Exception e) {
            // ❌ 异常被捕获,事务不会回滚
            log.error("Transfer failed", e);
        }
    }
}

✅ 解决方案 1:手动回滚

typescript 复制代码
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
    try {
        accountDao.debit(from, amount);
        accountDao.credit(to, amount);
    } catch (Exception e) {
        log.error("Transfer failed", e);
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();  // ✅ 手动回滚
    }
}

✅ 解决方案 2:重新抛出异常

typescript 复制代码
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
    try {
        accountDao.debit(from, amount);
        accountDao.credit(to, amount);
    } catch (Exception e) {
        log.error("Transfer failed", e);
        throw new RuntimeException(e);  // ✅ 重新抛出运行时异常
    }
}

❌ 场景 4:数据库引擎不支持事务
sql 复制代码
-- ❌ MyISAM 不支持事务
CREATE TABLE account (
    id INT PRIMARY KEY,
    balance DECIMAL(10, 2)
) ENGINE=MyISAM;

-- ✅ InnoDB 支持事务
CREATE TABLE account (
    id INT PRIMARY KEY,
    balance DECIMAL(10, 2)
) ENGINE=InnoDB;

检查数据库引擎:

ini 复制代码
SHOW TABLE STATUS WHERE Name = 'account';

❌ 场景 5:未被 Spring 管理
typescript 复制代码
// ❌ 没有 @Service 或其他组件注解
public class AccountService {
    
    @Transactional
    public void transfer() {
        // 事务无效 - 不是 Spring Bean
    }
}

✅ 解决方案:

typescript 复制代码
@Service  // ✅ 添加组件注解
public class AccountService {
    
    @Transactional
    public void transfer() {
        // 事务生效
    }
}

❌ 场景 6:异步方法
less 复制代码
@Service
public class NotificationService {
    
    @Async  // 异步执行
    @Transactional
    public void sendNotification(User user) {
        // ❌ 事务可能无效 - 异步方法在新线程中执行
    }
}

✅ 解决方案:分开事务和异步

typescript 复制代码
@Service
public class NotificationService {
    
    @Transactional
    public void prepareNotification(User user) {
        // 在事务中准备数据
        Notification notification = buildNotification(user);
        notificationDao.save(notification);
        
        // 异步发送
        sendNotificationAsync(notification);
    }
    
    @Async
    public void sendNotificationAsync(Notification notification) {
        // 异步发送,不在事务中
        emailService.send(notification);
    }
}

❌ 场景 7:多线程环境
typescript 复制代码
@Service
public class BatchService {
    
    @Transactional
    public void batchProcess(List<Order> orders) {
        // 主线程有事务
        orders.parallelStream().forEach(order -> {
            // ❌ 子线程中没有事务上下文
            processOrder(order);
        });
    }
    
    @Transactional
    public void processOrder(Order order) {
        // 在子线程中调用,事务无效
    }
}

✅ 解决方案:每个线程独立事务

typescript 复制代码
@Service
public class BatchService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void batchProcess(List<Order> orders) {
        orders.parallelStream().forEach(order -> {
            // 每个线程独立开启事务
            transactionTemplate.execute(status -> {
                processOrder(order);
                return null;
            });
        });
    }
    
    public void processOrder(Order order) {
        // 在 transactionTemplate 中执行
        orderDao.insert(order);
    }
}

五、编程式事务

5.1 TransactionTemplate

typescript 复制代码
@Service
public class AccountService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    @Autowired
    private AccountDao accountDao;
    
    public void transfer(String from, String to, BigDecimal amount) {
        // 编程式事务
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    accountDao.debit(from, amount);
                    accountDao.credit(to, amount);
                } catch (Exception e) {
                    status.setRollbackOnly();  // 手动回滚
                    throw e;
                }
            }
        });
    }
}

5.2 PlatformTransactionManager

typescript 复制代码
@Service
public class AccountService {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    @Autowired
    private AccountDao accountDao;
    
    public void transfer(String from, String to, BigDecimal amount) {
        // 定义事务属性
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        def.setTimeout(30);
        
        // 开启事务
        TransactionStatus status = transactionManager.getTransaction(def);
        
        try {
            accountDao.debit(from, amount);
            accountDao.credit(to, amount);
            
            // 提交事务
            transactionManager.commit(status);
            
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(status);
            throw e;
        }
    }
}

5.3 何时使用编程式事务?

场景 推荐方式
大多数业务场景 声明式事务(@Transactional)
细粒度控制事务边界 编程式事务
动态决定事务属性 编程式事务
批量处理中的部分提交 编程式事务
条件性回滚 编程式事务

六、分布式事务 ⭐⭐⭐⭐⭐

6.1 什么是分布式事务?

分布式事务是指跨越多个数据库、微服务或资源管理器的事务,需要保证这些分散的操作要么全部成功,要么全部失败。


6.2 分布式事务场景

css 复制代码
场景 1:微服务架构
┌──────────┐      ┌──────────┐      ┌──────────┐
│ 订单服务  │ ───→ │ 支付服务  │ ───→ │ 库存服务  │
│ MySQL    │      │ MySQL    │      │ MySQL    │
└──────────┘      └──────────┘      └──────────┘
     ↓                 ↓                 ↓
  创建订单           扣款              扣减库存
  
需要保证:三个操作要么全部成功,要么全部失败


场景 2:多数据源
┌──────────┐      ┌──────────┐
│ 用户库    │      │ 订单库    │
│ MySQL A  │      │ MySQL B  │
└──────────┘      └──────────┘
     ↓                 ↓
  创建用户           创建首单
  
需要保证:两个数据库操作的一致性

6.3 分布式事务解决方案

方案 1:两阶段提交(2PC - Two Phase Commit)

原理:

sql 复制代码
协调者(Transaction Coordinator)
       ↓
┌──────────────┐    ┌──────────────┐
│ 参与者 A      │    │ 参与者 B      │
│ (资源管理器)  │    │ (资源管理器)  │
└──────────────┘    └──────────────┘

第一阶段:准备(Prepare)
  协调者询问所有参与者:是否可以提交?
  参与者执行事务但不提交,返回 Yes/No

第二阶段:提交/回滚(Commit/Rollback)
  如果所有参与者都返回 Yes → 协调者通知所有人提交
  如果有任一参与者返回 No → 协调者通知所有人回滚

优点:

  • ✅ 强一致性
  • ✅ 实现相对简单

缺点:

  • ❌ 性能差(两次网络往返)
  • ❌ 阻塞协议(协调者故障会导致参与者阻塞)
  • ❌ 单点故障(协调者)

Spring 实现:

typescript 复制代码
@Configuration
public class JtaConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new JtaTransactionManager();
    }
}

@Service
public class DistributedService {
    
    @Transactional  // 使用 JTA 事务管理器
    public void distributedOperation() {
        // 操作数据源 A
        dataSourceA.executeUpdate(...);
        
        // 操作数据源 B
        dataSourceB.executeUpdate(...);
        
        // 自动进行 2PC
    }
}

方案 2:TCC(Try-Confirm-Cancel)

原理:

markdown 复制代码
Try 阶段:检查并预留资源
  - 验证业务可行性
  - 预留必要资源(如冻结库存)
  
Confirm 阶段:确认执行
  - 真正执行业务操作
  - 使用 Try 阶段预留的资源
  
Cancel 阶段:取消操作
  - 释放 Try 阶段预留的资源
  - 回滚操作

示例:

scss 复制代码
@Service
public class OrderTccService {
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private PaymentService paymentService;
    
    /**
     * Try 阶段:预留资源
     */
    @Transactional
    public boolean tryCreateOrder(Order order) {
        // 1. 预留库存
        boolean stockReserved = inventoryService.reserveStock(
            order.getProductId(), 
            order.getQuantity()
        );
        
        if (!stockReserved) {
            return false;
        }
        
        // 2. 冻结支付额度
        boolean paymentFrozen = paymentService.freezeAmount(
            order.getUserId(), 
            order.getAmount()
        );
        
        return paymentFrozen;
    }
    
    /**
     * Confirm 阶段:确认提交
     */
    @Transactional
    public void confirmCreateOrder(Order order) {
        // 1. 扣减库存(使用预留的)
        inventoryService.deductReservedStock(
            order.getProductId(), 
            order.getQuantity()
        );
        
        // 2. 扣款(使用冻结的)
        paymentService.deductFrozenAmount(
            order.getUserId(), 
            order.getAmount()
        );
        
        // 3. 创建订单
        orderDao.insert(order);
    }
    
    /**
     * Cancel 阶段:取消回滚
     */
    @Transactional
    public void cancelCreateOrder(Order order) {
        // 1. 释放预留库存
        inventoryService.releaseReservedStock(
            order.getProductId(), 
            order.getQuantity()
        );
        
        // 2. 解冻支付额度
        paymentService.unfreezeAmount(
            order.getUserId(), 
            order.getAmount()
        );
    }
}

优点:

  • ✅ 性能较好(无锁)
  • ✅ 最终一致性

缺点:

  • ❌ 业务侵入性强(需要实现三个方法)
  • ❌ 空回滚、悬挂等问题需要处理

框架支持:

  • Seata TCC
  • Hmily
  • ByteTCC

方案 3:本地消息表

原理:

复制代码
步骤 1:业务操作 + 写入消息表(同一事务)
步骤 2:定时任务扫描消息表,发送消息
步骤 3:下游服务消费消息,执行业务
步骤 4:下游服务返回成功,标记消息为已消费
步骤 5:定时任务清理已消费消息

示例:

scss 复制代码
@Service
public class OrderMessageService {
    
    @Autowired
    private OrderDao orderDao;
    
    @Autowired
    private MessageDao messageDao;
    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    /**
     * 创建订单并记录消息
     */
    @Transactional
    public void createOrderWithMessage(Order order) {
        // 1. 创建订单
        orderDao.insert(order);
        
        // 2. 记录消息(同一事务)
        Message message = new Message();
        message.setTopic("order-created");
        message.setKey(order.getId().toString());
        message.setContent(JSON.toJSONString(order));
        message.setStatus("PENDING");
        messageDao.insert(message);
    }
    
    /**
     * 定时任务:发送待处理消息
     */
    @Scheduled(fixedDelay = 5000)
    public void sendPendingMessages() {
        List<Message> pendingMessages = messageDao.findPendingMessages();
        
        for (Message message : pendingMessages) {
            try {
                // 发送消息
                kafkaTemplate.send(message.getTopic(), message.getKey(), message.getContent());
                
                // 标记为已发送
                message.setStatus("SENT");
                messageDao.update(message);
                
            } catch (Exception e) {
                log.error("Send message failed", e);
                // 下次重试
            }
        }
    }
}

/**
 * 下游服务:消费消息
 */
@Component
public class InventoryConsumer {
    
    @KafkaListener(topics = "order-created")
    @Transactional
    public void consumeOrderCreated(String message) {
        Order order = JSON.parseObject(message, Order.class);
        
        // 扣减库存
        inventoryService.deductStock(order.getProductId(), order.getQuantity());
        
        // 幂等性检查:避免重复消费
        // 如果已经处理过,直接返回
    }
}

优点:

  • ✅ 实现简单
  • ✅ 最终一致性
  • ✅ 解耦

缺点:

  • ❌ 延迟较高
  • ❌ 需要处理幂等性
  • ❌ 消息表维护成本

方案 4:Saga 模式

原理:

css 复制代码
正向操作:Service A → Service B → Service C
补偿操作:Service C 补偿 → Service B 补偿 → Service A 补偿

如果某步失败,依次执行前面所有步骤的补偿操作

示例:

scss 复制代码
@Service
public class OrderSagaService {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private InventoryService inventoryService;
    
    /**
     * Saga 编排器
     */
    public void createOrderSaga(Order order) {
        try {
            // 1. 创建订单
            orderService.createOrder(order);
            
            // 2. 支付
            paymentService.pay(order);
            
            // 3. 扣减库存
            inventoryService.deductStock(order);
            
        } catch (Exception e) {
            // 失败时执行补偿
            compensate(order);
            throw e;
        }
    }
    
    /**
     * 补偿操作
     */
    private void compensate(Order order) {
        // 3. 补偿:恢复库存
        try {
            inventoryService.restoreStock(order);
        } catch (Exception e) {
            log.error("Restore stock failed", e);
        }
        
        // 2. 补偿:退款
        try {
            paymentService.refund(order);
        } catch (Exception e) {
            log.error("Refund failed", e);
        }
        
        // 1. 补偿:取消订单
        try {
            orderService.cancelOrder(order.getId());
        } catch (Exception e) {
            log.error("Cancel order failed", e);
        }
    }
}

优点:

  • ✅ 长事务友好
  • ✅ 松耦合

缺点:

  • ❌ 补偿逻辑复杂
  • ❌ 可能出现不一致窗口期

框架支持:

  • Seata Saga
  • Axon Framework
  • Camunda

方案 5:最大努力通知

原理:

复制代码
步骤 1:业务操作完成后,发送通知
步骤 2:如果通知失败,定时重试
步骤 3:重试 N 次后仍失败,人工介入
步骤 4:下游服务保证幂等性

适用场景:

  • 对一致性要求不高
  • 允许短暂不一致
  • 如:订单创建后发送积分、优惠券

示例:

csharp 复制代码
@Service
public class PointsService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    /**
     * 最大努力通知:赠送积分
     */
    public void grantPointsAfterOrder(Order order) {
        int maxRetries = 5;
        int retryCount = 0;
        
        while (retryCount < maxRetries) {
            try {
                // 调用积分服务
                restTemplate.postForObject(
                    "http://points-service/grant",
                    new PointsRequest(order.getUserId(), order.getPoints()),
                    Void.class
                );
                
                // 成功,退出
                return;
                
            } catch (Exception e) {
                retryCount++;
                log.warn("Grant points failed, retry {}/{}", retryCount, maxRetries);
                
                // 指数退避
                try {
                    Thread.sleep((long) Math.pow(2, retryCount) * 1000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        
        // 重试耗尽,记录告警,人工介入
        log.error("Failed to grant points after {} retries", maxRetries);
        alertService.sendAlert("Points grant failed for order: " + order.getId());
    }
}

6.4 Seata 分布式事务框架 ⭐⭐⭐⭐⭐

Seata 是阿里巴巴开源的分布式事务解决方案,支持 AT、TCC、Saga、XA 四种模式。

Seata AT 模式(自动补偿事务)

原理:

markdown 复制代码
1. 一阶段:执行业务 SQL,生成 undo log
2. 二阶段:
   - 提交:删除 undo log
   - 回滚:根据 undo log 反向补偿

集成步骤:

1. 添加依赖

xml 复制代码
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.6.1</version>
</dependency>

2. 配置文件

yaml 复制代码
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  config:
    type: file
  registry:
    type: file

3. 使用 @GlobalTransactional

scss 复制代码
@Service
public class OrderService {
    
    @Autowired
    private PaymentFeignClient paymentClient;
    
    @Autowired
    private InventoryFeignClient inventoryClient;
    
    /**
     * 全局事务
     */
    @GlobalTransactional
    public void createOrder(Order order) {
        // 1. 创建订单(本地事务)
        orderDao.insert(order);
        
        // 2. 调用支付服务(远程事务)
        paymentClient.pay(order.getUserId(), order.getAmount());
        
        // 3. 调用库存服务(远程事务)
        inventoryClient.deductStock(order.getProductId(), order.getQuantity());
        
        // 如果任何一步失败,所有服务都会回滚
    }
}

4. 下游服务

typescript 复制代码
@Service
public class PaymentService {
    
    /**
     * 不需要 @GlobalTransactional
     * Seata 会自动加入全局事务
     */
    @Transactional
    public void pay(Long userId, BigDecimal amount) {
        // 扣款逻辑
        accountDao.debit(userId, amount);
    }
}

6.5 分布式事务选型建议

场景 推荐方案 理由
单体应用多数据源 2PC / JTA 简单可靠
微服务强一致性 Seata AT 自动补偿,侵入性小
微服务高性能 TCC 无锁,性能好
长事务流程 Saga 适合业务流程长的场景
最终一致性可接受 本地消息表 / MQ 解耦,可靠性高
通知类场景 最大努力通知 简单,成本低

七、事务最佳实践 ⭐⭐⭐⭐⭐

7.1 事务粒度控制

✅ 推荐:小事务
scss 复制代码
@Service
public class OrderService {
    
    @Transactional
    public void createOrder(Order order) {
        // 只包含必要的数据库操作
        orderDao.insert(order);
        orderItemDao.batchInsert(order.getItems());
    }
    
    // 耗时操作放在事务外
    public void createOrderWithNotification(Order order) {
        // 1. 事务内:创建订单
        createOrder(order);
        
        // 2. 事务外:发送通知
        sendEmail(order);
        sendSms(order);
    }
}

❌ 避免:大事务
scss 复制代码
@Service
public class BadPracticeService {
    
    @Transactional
    public void badExample(Order order) {
        // 数据库操作
        orderDao.insert(order);
        
        // ❌ HTTP 调用(耗时长,占用连接)
        restTemplate.postForObject("http://external-api/...", ...);
        
        // ❌ 文件 IO
        Files.write(...);
        
        // ❌ 复杂计算
        complexCalculation();
        
        // ❌ 等待用户输入
        waitForUserInput();
    }
}

7.2 避免事务嵌套过深

typescript 复制代码
// ❌ 不推荐:多层嵌套
@Transactional
public void methodA() {
    methodB();  // 嵌套
}

@Transactional
public void methodB() {
    methodC();  // 嵌套
}

@Transactional
public void methodC() {
    // ...
}

// ✅ 推荐:扁平化设计
@Transactional
public void processOrder(Order order) {
    validateOrder(order);      // 非事务方法
    saveOrder(order);          // 事务方法
    notifyStakeholders(order); // 非事务方法
}

7.3 合理使用只读事务

kotlin 复制代码
@Service
public class QueryService {
    
    // ✅ 查询方法使用 readOnly
    @Transactional(readOnly = true)
    public List<Order> findOrdersByUserId(Long userId) {
        return orderDao.findByUserId(userId);
    }
    
    // ✅ 批量查询使用 readOnly
    @Transactional(readOnly = true)
    public Map<Long, Product> findProductsByIds(List<Long> ids) {
        return productDao.findByIdIn(ids).stream()
            .collect(Collectors.toMap(Product::getId, p -> p));
    }
}

7.4 正确处理异常

typescript 复制代码
@Service
public class AccountService {
    
    @Transactional(rollbackFor = Exception.class)
    public void transfer(String from, String to, BigDecimal amount) {
        try {
            accountDao.debit(from, amount);
            accountDao.credit(to, amount);
            
        } catch (InsufficientBalanceException e) {
            // 业务异常,记录日志但不回滚
            log.warn("Insufficient balance: {}", from);
            throw e;  // 重新抛出,让调用方处理
            
        } catch (Exception e) {
            // 系统异常,记录日志并回滚
            log.error("Transfer failed", e);
            throw new SystemException("Transfer failed", e);
        }
    }
}

7.5 事务与缓存一致性

typescript 复制代码
@Service
public class ProductService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private ProductDao productDao;
    
    /**
     * 更新商品 - 先更新数据库,再删除缓存
     */
    @Transactional
    public void updateProduct(Product product) {
        // 1. 更新数据库
        productDao.update(product);
        
        // 2. 删除缓存(Cache Aside Pattern)
        redisTemplate.delete("product:" + product.getId());
    }
    
    /**
     * 查询商品 - 先查缓存,再查数据库
     */
    public Product getProduct(Long id) {
        // 1. 查缓存
        Product product = (Product) redisTemplate.opsForValue().get("product:" + id);
        if (product != null) {
            return product;
        }
        
        // 2. 查数据库
        product = productDao.findById(id);
        
        // 3. 写入缓存
        if (product != null) {
            redisTemplate.opsForValue().set("product:" + id, product, 1, TimeUnit.HOURS);
        }
        
        return product;
    }
}

7.6 监控和日志

less 复制代码
@Aspect
@Component
@Slf4j
public class TransactionMonitorAspect {
    
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object monitorTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().toShortString();
        
        try {
            Object result = joinPoint.proceed();
            
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed > 1000) {
                log.warn("Slow transaction: {} took {} ms", methodName, elapsed);
            }
            
            return result;
            
        } catch (Throwable e) {
            long elapsed = System.currentTimeMillis() - start;
            log.error("Transaction failed: {} after {} ms", methodName, elapsed, e);
            throw e;
        }
    }
}

八、常见问题 FAQ

Q1:@Transactional 能用在静态方法上吗?

A: ❌ 不能。Spring AOP 基于代理,静态方法无法被代理。


Q2:同一个类中方法调用,事务会生效吗?

A: ❌ 不会。这是自调用问题,需要通过代理调用才能生效。


Q3:事务方法中捕获异常后,事务还会回滚吗?

A: ❌ 不会。除非手动调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()


Q4:@Transactional 可以指定多个回滚异常吗?

A: ✅ 可以。

python 复制代码
@Transactional(rollbackFor = {SQLException.class, IOException.class})

Q5:分布式事务一定会影响性能吗?

A: 是的。分布式事务涉及多次网络调用和协调,性能必然低于本地事务。应根据业务需求选择合适的方案。


Q6:如何测试事务是否生效?

A:

  1. 故意抛出异常,观察数据是否回滚
  2. 使用 AOP 切面监控事务执行情况
  3. 查看数据库日志

Q7:事务超时时间设置多少合适?

A: 根据业务场景:

  • 简单 CRUD:5-10 秒
  • 复杂业务:30-60 秒
  • 批量处理:根据需要设置,但建议拆分批次

Q8:READ_COMMITTED 和 REPEATABLE_READ 如何选择?

A:

  • READ_COMMITTED:高并发场景,允许不可重复读(Oracle、SQL Server 默认)
  • REPEATABLE_READ:需要数据一致性,如财务计算(MySQL 默认)

九、总结

核心要点回顾

  1. ACID 特性:原子性、一致性、隔离性、持久性
  2. 传播行为:REQUIRED 最常用,REQUIRES_NEW 用于独立事务
  3. 隔离级别:根据并发需求选择,权衡一致性和性能
  4. 失效场景:注意自调用、异常捕获、非 public 方法等问题
  5. 分布式事务:根据场景选择 2PC、TCC、Saga、本地消息表等方案
  6. 最佳实践:小事务、只读优化、合理异常处理、监控日志

学习路线建议

css 复制代码
入门 → 掌握 @Transactional 基本使用
  ↓
进阶 → 理解传播行为、隔离级别、失效场景
  ↓
高级 → 学习分布式事务解决方案
  ↓
专家 → 深入源码,理解 Spring 事务实现原理

参考资料:

相关推荐
程序员陆业聪1 小时前
网络监控与容灾:让网络问题无处遁形 | Android网络优化系列(5·完结)
后端
fliter1 小时前
Rust 能帮你捕获什么,又不能捕获什么
后端
ZHOUPUYU1 小时前
PHP8高性能Web开发实战指南
后端·html·php
fliter1 小时前
一个 Emoji 是怎么让 rust-analyzer 崩溃的
后端
天涯明月19931 小时前
AEnvironment深度研究报告
人工智能·后端·云原生
springXu1 小时前
windows arm64上的VS CODE的GoLang环境的搭建
开发语言·后端·golang
怕浪猫2 小时前
听说后端又死了?AI 时代前端后端都怎么样了
后端·面试
IT_陈寒2 小时前
Redis突然吃掉所有内存,我的服务差点挂了
前端·人工智能·后端
鹏程十八少2 小时前
Android TransactionTooLargeException 的真相与修复:从 1.13MB Bundle 到 Binder 内核的完整剖析
前端·后端·面试