事务
为什么需要事务
转账这类操作由「扣款 + 入账」多步组成,任何一步失败都应整体撤销,否则账目不平。事务把一组操作打包成原子单元:全部成功才提交,任一失败就回滚。Spring 的声明式事务用 @Transactional 声明边界,由代理在方法前后自动开启、提交或回滚,业务代码零样板。
开启方式
Spring Boot 检测到事务管理器后会自动开启事务管理(spring-boot-starter-jdbc、spring-boot-starter-data-jpa 都会带来事务管理器),直接使用 @Transactional 即可,无需手工加 @EnableTransactionManagement。
基本用法
@Service
public class TransferService {
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
accountDao.decrease(fromId, amount);
accountDao.increase(toId, amount);
// 任一语句抛异常,两步一起回滚
}
}
要点:
- 可加在类上(该类所有 public 方法生效),方法级注解优先;
- 只对 public 方法生效(通过代理实现,非 public 方法不经过代理);
- 默认只对
RuntimeException与Error回滚;抛受检异常时不会回滚,需要显式rollbackFor = Exception.class; - 常用属性还有
isolation(隔离级别)、timeout(超时秒数)、readOnly = true(只读事务,适合批量查询场景);语义细节以官方文档为准。
传播行为
方法调用链上已有事务时,「是否新建、是否加入」由传播属性决定,常用三种:
| 传播 | 行为 | 典型场景 |
|---|---|---|
| REQUIRED(默认) | 有事务则加入,没有则新建 | 大多数业务方法 |
| REQUIRES_NEW | 挂起当前事务,另起新事务 | 操作日志落库,失败不影响主事务 |
| NESTED | 嵌套保存点,内层回滚只回退到保存点 | 分批处理中某批失败不影响已成功批次 |
其余 SUPPORTS/NOT_SUPPORTED/MANDATORY/NEVER 了解即可,完整语义以官方文档为准。
常见失效场景
- 自调用:同类方法里
this.xxx()直接调用,不经代理,事务不生效。解决:把需要事务的方法放进另一个 Bean,或注入自身的代理。 - 异常被吞:方法内
try-catch把异常吃掉,事务管理器看不到异常自然不会回滚。应让异常向上抛出,或只捕获后重新抛出。 - 非 public 方法加
@Transactional不生效。 - 多线程:事务方法内新开线程执行的代码没有事务上下文,不在同一事务内。
多数据源场景
多数据源时 Spring 的自动配置会后退,需要自己声明数据源与事务管理器,并指定主数据源:
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSourceTransactionManager primaryTx(DataSource primaryDataSource) {
return new DataSourceTransactionManager(primaryDataSource);
}
@Bean
public DataSourceTransactionManager secondaryTx(DataSource secondaryDataSource) {
return new DataSourceTransactionManager(secondaryDataSource);
}
}
对应的配置键(Hikari 使用 jdbc-url 约定)与 DataSourceBuilder 的行为细节以官方文档为准。使用时按事务管理器名指定:
@Transactional("secondaryTx") // 默认主事务管理器时可不写
public void writeSecondary() { ... }
注意:普通数据库事务只保证单库原子性。跨库、跨系统操作需要分布式事务(如 Seata、本地消息表等方案),超出本教程范围,请查阅对应方案的官方文档。
小结
@Transactional 用好了是一行注解的事,用坏了是「假事务」。记住三条:public + 代理入口 + 让异常抛出来;多数据源时各配各的事务管理器、用注解名区分。