在 Spring Boot 开发中,@Transactional 注解几乎是处理数据库事务的标配,但实际开发中经常遇到“注解加了,事务却没生效”的情况。本文梳理了 8 种高频的事务失效场景,结合具体代码示例分析原因,并给出可落地的避坑方案,帮你彻底搞定事务失效问题。
一、先搞懂:Spring 事务的核心原理
在分析失效场景前,先明确 Spring 声明式事务的核心逻辑——基于 AOP 动态代理实现:
- Spring 会为标注
@Transactional的类生成代理对象; - 只有通过代理对象调用事务方法时,才会触发事务拦截器,创建/提交/回滚事务;
- 若绕过代理直接调用目标方法,事务注解会完全失效。
这是理解所有事务失效场景的基础,记住:事务生效的前提是“走代理”。
二、8 大事务失效场景全解析
场景 1:事务方法被 private/final/static 修饰
失效原因
Spring AOP 基于动态代理实现,而代理的本质是生成子类重写目标方法:
private方法:子类无法重写,代理无法拦截;final方法:子类不能重写,代理逻辑无法植入;static方法:属于类而非实例,代理对象无法调用。
示例代码(失效)
@Service
public class OrderService {
@Resource
private JdbcTemplate jdbcTemplate;
// private 修饰,事务失效
@Transactional(rollbackFor = Exception.class)
private void createOrder(Long productId, Long userId) {
jdbcTemplate.update("UPDATE t_stock SET count = count - 1 WHERE product_id = ?", productId);
jdbcTemplate.update("INSERT INTO t_order (user_id, product_id) VALUES (?, ?)", userId, productId);
// 抛异常也不会回滚
throw new RuntimeException("下单失败");
}
// final 修饰,事务失效
@Transactional(rollbackFor = Exception.class)
public final void deductStock(Long productId) {
jdbcTemplate.update("UPDATE t_stock SET count = count - 1 WHERE product_id = ?", productId);
}
// static 修饰,事务失效
@Transactional(rollbackFor = Exception.class)
public static void saveLog(String content) {
jdbcTemplate.update("INSERT INTO t_log (content) VALUES (?)", content);
}
}
避坑方案
- 事务方法必须是 public 非 final 非 static 修饰;
- 若需封装私有逻辑,可将事务逻辑抽离到 public 方法,私有方法仅做纯业务处理:
@Service
public class OrderService {
// public 方法,事务生效
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long productId, Long userId) {
// 调用私有方法(仅做业务逻辑)
doCreateOrder(productId, userId);
}
// 私有方法仅处理业务,不标注事务
private void doCreateOrder(Long productId, Long userId) {
jdbcTemplate.update("UPDATE t_stock SET count = count - 1 WHERE product_id = ?", productId);
jdbcTemplate.update("INSERT INTO t_order (user_id, product_id) VALUES (?, ?)", userId, productId);
}
}
场景 2:本类内部调用事务方法
失效原因
本类内部调用时,直接调用目标对象的方法,而非代理对象,事务拦截器未触发,注解失效。
示例代码(失效)
@Service
public class OrderService {
@Resource
private JdbcTemplate jdbcTemplate;
// 非事务方法
public void outerMethod(Long productId, Long userId) {
// 内部调用事务方法,绕过代理,事务失效
innerTransactionalMethod(productId, userId);
}
// 事务方法(被内部调用)
@Transactional(rollbackFor = Exception.class)
public void innerTransactionalMethod(Long productId, Long userId) {
jdbcTemplate.update("UPDATE t_stock SET count = count - 1 WHERE product_id = ?", productId);
jdbcTemplate.update("INSERT INTO t_order (user_id, product_id) VALUES (?, ?)", userId, productId);
throw new RuntimeException("下单失败"); // 不会回滚
}
}
避坑方案
核心思路:让调用走代理对象,有 3 种实现方式:
方案 1:注入自身代理对象(推荐)
@Service
public class OrderService {
// 注入自身代理对象(需开启 expose-proxy = true)
@Resource
private OrderService selfProxy;
public void outerMethod(Long productId, Long userId) {
// 通过代理对象调用,事务生效
selfProxy.innerTransactionalMethod(productId, userId);
}
@Transactional(rollbackFor = Exception.class)
public void innerTransactionalMethod(Long productId, Long userId) {
// 事务逻辑...
}
}
注意:需在启动类开启代理暴露:
@EnableTransactionManagement(exposeProxy = true) // 关键配置
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
方案 2:拆分 Bean(最彻底)
将事务方法拆分到独立 Bean,通过跨 Bean 调用触发代理:
// 独立的事务 Bean
@Service
public class OrderTransactionService {
@Resource
private JdbcTemplate jdbcTemplate;
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long productId, Long userId) {
// 事务逻辑...
}
}
// 原 Bean 调用独立 Bean
@Service
public class OrderService {
@Resource
private OrderTransactionService transactionService;
public void outerMethod(Long productId, Long userId) {
// 跨 Bean 调用,事务生效
transactionService.createOrder(productId, userId);
}
}
场景 3:异常被“吞掉”未抛出
失效原因
Spring 事务回滚的前提是:事务方法抛出未被捕获的异常。若异常被内部捕获且未重新抛出,事务管理器无法感知异常,会默认提交事务。
示例代码(失效)
@Service
public class OrderService {
@Resource
private JdbcTemplate jdbcTemplate;
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long productId, Long userId) {
try {
jdbcTemplate.update("UPDATE t_stock SET count = count - 1 WHERE product_id = ?", productId);
jdbcTemplate.update("INSERT INTO t_order (user_id, product_id) VALUES (?, ?)", userId, productId);
throw new RuntimeException("下单失败");
} catch (Exception e) {
// 捕获异常但不抛出,事务不会回滚
log.error("下单异常", e);
}
}
}
避坑方案
- 异常必须抛到事务方法外层,让事务管理器感知;
- 若需捕获异常做日志/补偿,捕获后重新抛出:
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long productId, Long userId) {
try {
// 业务逻辑
} catch (Exception e) {
log.error("下单异常", e);
// 重新抛出异常,触发事务回滚
throw new RuntimeException("下单失败", e);
}
}
场景 4:未指定 rollbackFor,检查型异常导致不回滚
失效原因
@Transactional 默认仅回滚 RuntimeException 和 Error,若抛出检查型异常(如 IOException、SQLException),事务不会回滚。
示例代码(失效)
@Service
public class OrderService {
@Resource
private JdbcTemplate jdbcTemplate;
// 未指定 rollbackFor,检查型异常不回滚
@Transactional
public void createOrder(Long productId, Long userId) throws Exception {
jdbcTemplate.update("UPDATE t_stock SET count = count - 1 WHERE product_id = ?", productId);
jdbcTemplate.update("INSERT INTO t_order (user_id, product_id) VALUES (?, ?)", userId, productId);
// 抛出检查型异常,事务不回滚
throw new Exception("下单失败");
}
}
避坑方案
强制指定 rollbackFor = Exception.class,覆盖所有异常类型:
// 指定 rollbackFor,所有异常都回滚
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long productId, Long userId) throws Exception {
// 业务逻辑...
throw new Exception("下单失败"); // 事务会回滚
}
场景 5:传播行为配置错误
失效原因
若事务方法的传播行为配置为 SUPPORTS/NOT_SUPPORTED/NEVER,且调用方无事务上下文,事务会以非事务方式执行,注解失效。
示例代码(失效)
@Service
public class OrderService {
@Resource
private JdbcTemplate jdbcTemplate;
// 传播行为为 SUPPORTS,调用方无事务则非事务执行
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public void createOrder(Long productId, Long userId) {
jdbcTemplate.update("UPDATE t_stock SET count = count - 1 WHERE product_id = ?", productId);
jdbcTemplate.update("INSERT INTO t_order (user_id, product_id) VALUES (?, ?)", userId, productId);
throw new RuntimeException("下单失败"); // 不会回滚
}
}
// 调用方无事务
@Controller
public class OrderController {
@Resource
private OrderService orderService;
@PostMapping("/order")
public ResponseEntity<String> createOrder(@RequestParam Long productId, @RequestParam Long userId) {
orderService.createOrder(productId, userId); // 无事务上下文,SUPPORTS 以非事务执行
return ResponseEntity.ok("下单成功");
}
}
避坑方案
- 核心业务方法默认用
Propagation.REQUIRED(默认值,可省略); - 仅查询场景用
SUPPORTS,且需确保核心写操作不依赖该传播行为:
// 核心写操作用 REQUIRED(默认)
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long productId, Long userId) {
// 业务逻辑...
}
场景 6:数据源未配置事务管理器
失效原因
Spring Boot 自动配置的事务管理器仅对默认数据源生效,若自定义多数据源但未手动配置事务管理器,事务注解会失效。
示例代码(失效)
// 自定义多数据源,但未配置事务管理器
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
// 事务注解失效,因为无对应事务管理器
@Service
public class OrderService {
@Resource
@Qualifier("primaryDataSource")
private JdbcTemplate jdbcTemplate;
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long productId, Long userId) {
// 业务逻辑...
}
}
避坑方案
为每个自定义数据源配置事务管理器:
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
// 配置主数据源事务管理器
@Bean(name = "primaryTransactionManager")
@Primary
public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 从数据源同理...
}
场景 7:数据库不支持事务
失效原因
部分数据库/存储引擎不支持事务(如 MySQL 的 MyISAM 引擎、SQLite 非 WAL 模式),即使加了 @Transactional,也无法保证事务原子性。
示例代码(失效)
-- MySQL 表使用 MyISAM 引擎,不支持事务
CREATE TABLE t_order (
id BIGINT PRIMARY KEY,
user_id BIGINT,
product_id BIGINT
) ENGINE=MyISAM;
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long productId, Long userId) {
// 插入订单后抛异常,但 MyISAM 不回滚
jdbcTemplate.update("INSERT INTO t_order (user_id, product_id) VALUES (?, ?)", userId, productId);
throw new RuntimeException("下单失败");
}
}
避坑方案
- 确保数据库/表使用支持事务的引擎(如 MySQL 用 InnoDB);
- 检查表引擎:
-- 查看表引擎
SHOW TABLE STATUS LIKE 't_order';
-- 修改为 InnoDB
ALTER TABLE t_order ENGINE=InnoDB;
场景 8:事务超时时间过短,导致提前回滚(隐性失效)
失效原因
事务方法执行时间超过 timeout 配置的时间,事务管理器会强制回滚,看似“失效”,实则是超时触发回滚。
示例代码(失效)
@Service
public class OrderService {
// 超时时间 1 秒,业务执行超时报错
@Transactional(rollbackFor = Exception.class, timeout = 1)
public void createOrder(Long productId, Long userId) throws InterruptedException {
// 模拟耗时操作(2 秒)
Thread.sleep(2000);
jdbcTemplate.update("INSERT INTO t_order (user_id, product_id) VALUES (?, ?)", userId, productId);
}
}
避坑方案
- 合理设置超时时间,避免过短;
- 将耗时操作(如远程调用、IO)移出事务:
@Transactional(rollbackFor = Exception.class, timeout = 30) // 合理超时时间
public void createOrder(Long productId, Long userId) {
// 非事务耗时操作:先查远程价格
BigDecimal price = productRemoteService.getPrice(productId);
// 事务内仅执行数据库操作(短事务)
jdbcTemplate.update("INSERT INTO t_order (user_id, product_id, price) VALUES (?, ?, ?)", userId, productId, price);
}
三、事务失效排查清单(快速定位问题)
- 事务方法是否为
public 非 final 非 static? - 是否存在本类内部调用事务方法?
- 异常是否被捕获未抛出?
- 是否指定
rollbackFor = Exception.class? - 传播行为是否为
REQUIRED(核心写操作)? - 自定义数据源是否配置了事务管理器?
- 数据库/表是否支持事务(如 InnoDB)?
- 事务超时时间是否合理?
四、总结
Spring Boot 事务失效的核心原因可归纳为三类:
- 代理未触发:方法修饰符错误、本类内部调用;
- 异常未感知:异常被吞、未指定 rollbackFor;
- 配置/环境问题:传播行为错误、事务管理器缺失、数据库不支持事务。
只要抓住“走代理、抛异常、配对参数”三个核心,99% 的事务失效问题都能解决。日常开发中,建议遵循“最简配置原则”:
- 仅给外层入口方法加
@Transactional(rollbackFor = Exception.class); - 被调用方法不加事务注解,专注业务逻辑;
- 避免滥用传播行为,核心场景用默认的
REQUIRED。
随时随地看视频