事务管理

Spring 通过 @Transactional 注解将事务管理从业务代码中剥离,底层依赖 AOP 代理拦截方法调用。

快速使用

@Service
@RequiredArgsConstructor
public class OrderService {
 
    private final OrderRepository orderRepo;
    private final InventoryRepository inventoryRepo;
 
    @Transactional   // 默认:REQUIRED 传播 + DEFAULT 隔离 + 运行时异常回滚
    public Order createOrder(CreateOrderRequest req) {
        Order order = orderRepo.save(buildOrder(req));
        inventoryRepo.deduct(req.getProductId(), req.getQuantity());
        // 任意 RuntimeException → 事务回滚
        return order;
    }
 
    @Transactional(readOnly = true)   // 只读事务:跳过脏检查,部分数据库可路由到从库
    public Order getOrder(Long id) {
        return orderRepo.findById(id).orElseThrow();
    }
}

传播行为(Propagation)

控制当前方法与已有事务的关系,默认 REQUIRED

传播行为有事务时无事务时典型场景
REQUIRED(默认)加入已有事务新建事务普通业务方法
REQUIRES_NEW挂起已有,新建独立事务新建事务操作日志、独立提交
NESTED创建嵌套事务(保存点)新建事务部分回滚
SUPPORTS加入已有事务非事务执行查询(可选事务)
NOT_SUPPORTED挂起已有,非事务执行非事务执行不需要事务的批量操作
MANDATORY加入已有事务抛出异常必须由调用方开启事务
NEVER抛出异常非事务执行强制禁止事务

REQUIRES_NEW vs NESTED 的区别

REQUIRES_NEW:两个独立事务
  外层事务 ────────────────────────────────────► 提交/回滚
                 内层事务(独立)─────► 提交/回滚(不受外层影响)

NESTED:一个事务 + 保存点
  外层事务 ─────────[savepoint]──────────────── 提交/回滚
                         内层 ─► 回滚到 savepoint(外层继续)
@Transactional
public void processOrder(Order order) {
    orderRepo.save(order);
 
    try {
        // REQUIRES_NEW:无论 processOrder 是否回滚,操作日志都会保存
        auditService.recordLog(order);
    } catch (Exception e) {
        // 操作日志失败不影响主流程
        log.warn("记录日志失败", e);
    }
}
 
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordLog(Order order) {
    auditLogRepo.save(new AuditLog(order));
}

隔离级别(Isolation)

隔离级别脏读不可重复读幻读说明
READ_UNCOMMITTED最低,几乎不用
READ_COMMITTEDPostgreSQL / Oracle 默认
REPEATABLE_READMySQL InnoDB 默认
SERIALIZABLE最高,性能最差
DEFAULT使用数据库默认隔离级别(推荐)
// 报表统计:防止两次查询结果不一致
@Transactional(isolation = Isolation.REPEATABLE_READ, readOnly = true)
public ReportData generateReport(LocalDate date) { ... }

回滚规则

@Transactional 默认只回滚 RuntimeException 及其子类,受检异常(ExceptionIOException 等)不回滚

// 显式声明回滚受检异常
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) throws Exception {
    accountRepo.debit(fromId, amount);
    accountRepo.credit(toId, amount);
}
 
// 排除某类异常不回滚(如库存告警,不影响主流程)
@Transactional(noRollbackFor = InventoryWarningException.class)
public void createOrder(Order order) throws InventoryWarningException {
    orderRepo.save(order);
    inventoryService.checkAndWarn(order);  // 警告但不回滚
}

手动触发回滚:

@Transactional
public void process() {
    try {
        doSomething();
    } catch (BusinessException e) {
        // 捕获了异常又想回滚:标记当前事务为回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        log.warn("业务处理失败,事务将回滚: {}", e.getMessage());
    }
}

事务失效的常见场景

1. 同类内部调用(最常见)

AOP 代理只对通过代理对象的调用生效,同类方法直接互调绕过代理:

@Service
public class OrderService {
 
    @Transactional
    public void createOrder(Order order) {
        // ❌ 直接调用,不经过代理,sendNotification 的事务不生效
        sendNotification(order);
    }
 
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendNotification(Order order) { ... }
}
 
// 解决方案一:注入自身代理
@Service
public class OrderService {
 
    @Autowired
    private OrderService self;  // 注入代理对象
 
    @Transactional
    public void createOrder(Order order) {
        self.sendNotification(order);  // ✅ 通过代理调用
    }
}
 
// 解决方案二:拆分到独立 Bean
@Service
public class NotificationService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void send(Order order) { ... }
}

2. 方法非 public

Spring AOP 默认只拦截 public 方法,protected / private 方法上的 @Transactional 无效。

3. 异常被吞掉

@Transactional
public void process() {
    try {
        riskyOperation();
    } catch (Exception e) {
        // ❌ 异常被捕获且未重新抛出,事务认为方法正常完成,不回滚
        log.error("失败", e);
    }
}
 
// 修复:捕获后重新抛出,或调用 setRollbackOnly()

4. 多数据源

@Transactional 只管理单个 DataSource,跨数据源操作需要分布式事务。多数据源详见 多数据源

5. 非 Spring 管理的对象

new OrderService() 创建的对象不经过 Spring 容器,没有代理,事务不生效。


编程式事务

注解方式声明式事务适合大多数场景,编程式事务适合动态控制(如循环中对每条记录单独事务):

@Service
@RequiredArgsConstructor
public class BatchService {
 
    private final TransactionTemplate transactionTemplate;
 
    public void batchProcess(List<Item> items) {
        for (Item item : items) {
            // 每条记录独立事务,互不影响
            transactionTemplate.executeWithoutResult(status -> {
                try {
                    processItem(item);
                } catch (BusinessException e) {
                    log.warn("处理失败,跳过: itemId={}", item.getId());
                    status.setRollbackOnly();  // 仅回滚当前条目
                }
            });
        }
    }
 
    // 需要返回值
    public Order createWithResult(CreateOrderRequest req) {
        return transactionTemplate.execute(status -> {
            return orderRepo.save(buildOrder(req));
        });
    }
}
// TransactionTemplate Bean 注册
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager manager) {
    TransactionTemplate template = new TransactionTemplate(manager);
    template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
    return template;
}

事务与异步

@Async 方法在新线程中运行,不继承父线程的事务上下文

@Transactional
public void createOrder(Order order) {
    orderRepo.save(order);
    // ❌ 异步方法在新线程中,没有事务,且 order 可能尚未提交就被读取
    asyncService.sendEmailAsync(order);
}
 
// 正确做法:使用 @TransactionalEventListener,在事务提交后触发

事件机制与事务的配合详见 事件机制


只读事务

@Transactional(readOnly = true)
public List<Order> listOrders(Long userId) { ... }

readOnly = true 的效果:

  • JPA / Hibernate:跳过脏检查(FlushMode = MANUAL),减少内存和 CPU 消耗
  • 支持读写分离的连接池:可路由到从库
  • 某些数据库驱动会优化只读连接

分布式事务

跨多个数据库或服务的事务,Spring 内置事务无法支持,常用方案:

方案一致性性能实现复杂度
Seata AT强一致低(注解驱动)
Seata TCC强一致高(手写 try/confirm/cancel)
本地消息表 + MQ最终一致
Saga最终一致
// Seata 全局事务(AT 模式)
@GlobalTransactional   // Seata 注解,替代 @Transactional
public void placeOrder(OrderRequest req) {
    orderService.create(req);      // 服务A,数据库A
    inventoryService.deduct(req);  // 服务B,数据库B
    pointService.award(req);       // 服务C,数据库C
}

相关链接

  • AOP@Transactional 底层的代理机制与切面执行顺序
  • 事件机制@TransactionalEventListener 在事务提交后触发事件
  • 多数据源 — 多数据源场景的事务管理
  • 分布式锁 — 分布式锁与事务的边界关系(锁应在事务外层)
  • JPA与Hibernate — JPA 脏检查与只读事务的优化
  • MyBatis — MyBatis 中的事务管理
  • 数据库迁移 — 迁移脚本的事务控制