📌 前言
在Spring开发中,@Transactional 注解用起来很方便,但很多开发者都遇到过“明明加了事务注解,但事务就是不生效”的诡异问题。
本文总结了 15种 Spring事务失效的典型场景,每一种都有详细原因分析和解决方案,建议收藏!
🎯 事务失效的核心原因
Spring事务的本质: 基于AOP动态代理,通过代理对象调用方法才能增强事务。
一句话总结: 事务失效 = 代理没生效 = 方法没有被Spring代理对象调用
❌ 场景1:方法没有被public修饰
错误示例
java
@Service
public class UserService {
@Transactional
private void updateUser() { // ❌ private方法
// 事务不会生效
}
}
原因
Spring事务代理默认只能对 public 方法进行增强,private/protected/默认 方法都不会生效。
✅ 解决方案
java
@Service
public class UserService {
@Transactional
public void updateUser() { // ✅ 改为public
// 事务生效
}
}
❌ 场景2:同类中方法内部调用
错误示例
java
@Service
public class UserService {
public void save() {
update(); // ❌ 内部调用,绕过了代理
}
@Transactional
public void update() {
// 事务不会生效
}
}
原因
内部调用是直接调用本类方法,没有经过Spring代理,事务无法增强。
✅ 解决方案(3种)
方案1: 将事务方法放到另一个Service
java
@Service
public class UserService {
@Autowired
private UpdateService updateService;
public void save() {
updateService.update(); // ✅ 通过代理调用
}
}
方案2: 获取代理对象调用
java
@Service
public class UserService {
@Autowired
private ApplicationContext context;
public void save() {
UserService proxy = context.getBean(UserService.class);
proxy.update(); // ✅ 通过代理调用
}
@Transactional
public void update() {
// 事务生效
}
}
方案3: 自己注入自己(推荐)
java
@Service
public class UserService {
@Autowired
private UserService self; // 注入代理对象
public void save() {
self.update(); // ✅ 通过代理调用
}
@Transactional
public void update() {
// 事务生效
}
}
❌ 场景3:异常被try-catch吞掉了
错误示例
java
@Service
public class UserService {
@Transactional
public void update() {
try {
// 数据库操作
int i = 1 / 0; // 抛出异常
} catch (Exception e) {
// ❌ 异常被捕获,事务不会回滚
System.out.println("异常被吞了");
}
}
}
原因
Spring事务回滚依赖于异常抛出,异常被捕获后事务管理器收不到信号。
✅ 解决方案
方案1: 手动回滚
java
@Transactional
public void update() {
try {
// 数据库操作
} catch (Exception e) {
// ✅ 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
方案2: 重新抛出异常
java
@Transactional
public void update() throws Exception {
try {
// 数据库操作
} catch (Exception e) {
// ✅ 重新抛出
throw e;
}
}
方案3: 不捕获(最推荐)
java
@Transactional
public void update() {
// 不捕获,让异常自动抛出
}
❌ 场景4:抛出的事务不回滚的异常类型
错误示例
java
@Transactional
public void update() throws Exception {
// 数据库操作
throw new Exception(); // ❌ Exception不会触发回滚
}
原因
Spring默认只对 RuntimeException 和 Error 进行回滚,Exception 不回滚。
✅ 解决方案
方案1: 指定回滚异常类型
java
@Transactional(rollbackFor = Exception.class) // ✅ 指定
public void update() throws Exception {
throw new Exception(); // 现在会回滚
}
方案2: 抛出RuntimeException
java
@Transactional
public void update() {
throw new RuntimeException(); // ✅ 默认会回滚
}
❌ 场景5:数据库引擎不支持事务
错误示例
java
// MySQL 使用 MyISAM 引擎 // MyISAM 不支持事务!
原因
MySQL的MyISAM存储引擎不支持事务,需要InnoDB。
✅ 解决方案
sql
-- 查看表引擎 SHOW TABLE STATUS WHERE Name = 'user'; -- 修改为 InnoDB ALTER TABLE user ENGINE = InnoDB;
❌ 场景6:多数据源配置错误
错误示例
java
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource dataSource1() { ... }
@Bean
public DataSource dataSource2() { ... }
// ❌ 没有配置对应的 TransactionManager
}
✅ 解决方案
java
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource dataSource1() { ... }
@Bean
public PlatformTransactionManager transactionManager1() {
return new DataSourceTransactionManager(dataSource1());
}
@Bean
public DataSource dataSource2() { ... }
@Bean
public PlatformTransactionManager transactionManager2() {
return new DataSourceTransactionManager(dataSource2());
}
}
@Service
public class UserService {
@Transactional(transactionManager = "transactionManager1")
public void update1() { ... }
@Transactional(transactionManager = "transactionManager2")
public void update2() { ... }
}
❌ 场景7:事务传播特性使用不当
错误示例
java
@Service
public class OuterService {
@Transactional
public void outer() {
innerService.inner(); // 调用内层方法
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// ❌ 外层异常会影响内层已提交的数据
}
}
解决方案
理解7种传播特性的含义,根据业务选择正确的传播级别。
| 传播级别 | 说明 |
|---|---|
| REQUIRED | 默认,支持当前事务,没有就新建 |
| REQUIRES_NEW | 新建事务,挂起当前事务 |
| NESTED | 嵌套事务,回滚到保存点 |
| SUPPORTS | 支持当前事务,没有就以非事务运行 |
| NOT_SUPPORTED | 非事务运行,挂起当前事务 |
| MANDATORY | 必须有当前事务,否则抛异常 |
| NEVER | 必须非事务运行,否则抛异常 |
❌ 场景8:@Transactional 加在非Spring管理的类上
错误示例
java
// ❌ 普通类,没有被Spring管理
public class UserService {
@Transactional // 完全无效
public void update() { }
}
原因
@Transactional 只有被Spring IoC容器管理的Bean才会生效。
✅ 解决方案
java
@Service // ✅ 加上注解,交给Spring管理
public class UserService {
@Transactional
public void update() { }
}
❌ 场景9:方法中有多个数据库操作,但未开启事务
错误示例
java
@Service
public class OrderService {
public void createOrder() { // ❌ 没有事务注解
orderDao.insert(); // 第1步成功
inventoryDao.update(); // 第2步失败
// 第1步不会回滚!
}
}
✅ 解决方案
java
@Service
public class OrderService {
@Transactional // ✅ 加上事务注解
public void createOrder() {
orderDao.insert();
inventoryDao.update(); // 失败时全部回滚
}
}
❌ 场景10:事务超时设置不合理
错误示例
java
@Transactional(timeout = 1) // 超时1秒
public void longRunningMethod() {
// 执行超过1秒的操作
// ❌ 事务会被自动回滚
}
✅ 解决方案
java
// 根据业务需求设置合适的超时时间
@Transactional(timeout = 30) // 30秒
public void longRunningMethod() {
// 业务操作
}
❌ 场景11:只读事务中执行写操作
错误示例
java
@Transactional(readOnly = true)
public void updateUser() { // ❌ 只读事务中执行更新
userDao.update(); // 可能抛出异常
}
✅ 解决方案
java
@Transactional(readOnly = false) // 或直接不写
public void updateUser() {
userDao.update();
}
❌ 场景12:Spring版本兼容性问题
错误示例
java
// Spring 3.x 对 @Transactional 的支持不完善 // 某些配置方式可能导致事务失效
✅ 解决方案
java
// 确保配置正确
@Configuration
@EnableTransactionManagement // 开启事务支持
public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
❌ 场景13:使用了错误的代理模式
错误示例
java
// 默认使用JDK动态代理(基于接口) // 如果类没有实现接口,代理失败
✅ 解决方案
java
// 方案1:让类实现接口
public interface UserService { ... }
@Service
public class UserServiceImpl implements UserService { ... }
// 方案2:强制使用CGLIB代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Configuration
public class AppConfig { ... }
❌ 场景14:事务隔离级别设置不当
错误示例
java
@Transactional(isolation = Isolation.SERIALIZABLE)
public void update() {
// SERIALIZABLE 级别性能极差,可能导致死锁
}
✅ 解决方案
java
// 根据业务需求选择合适的隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED) // 最常用
public void update() { ... }
❌ 场景15:多线程环境下事务失效
错误示例
java
@Transactional
public void batchUpdate() {
// ❌ 每个子线程的事务独立
taskExecutor.submit(() -> {
userDao.update(); // 异常不会影响主事务
});
}
原因
Spring事务和线程绑定(ThreadLocal),子线程的事务独立。
✅ 解决方案
java
// 方案1:不使用多线程
@Transactional
public void batchUpdate() {
for (User user : userList) {
userDao.update(user); // 同步执行
}
}
// 方案2:使用编程式事务
@Autowired
private TransactionTemplate transactionTemplate;
public void batchUpdate() {
transactionTemplate.execute(status -> {
// 多线程操作...
return null;
});
}
📊 问题速查表
| 症状 | 可能原因 |
|---|---|
| 事务完全没生效 | 方法不是public / 类没被Spring管理 / 内部调用 |
| 异常后不回滚 | 异常被catch吞掉 / 抛出了非RuntimeException |
| 部分回滚 | 传播特性配置错误 / 多线程 |
| 数据库没变化 | 引擎不支持事务 / 只读事务 |
✅ 最佳实践总结
-
统一规范:
@Transactional加在public方法上 -
明确回滚:需要回滚时指定
rollbackFor = Exception.class -
避免内部调用:通过代理对象调用事务方法
-
不要吞异常:要么不catch,要么手动回滚
-
数据库配置:确认使用 InnoDB 引擎
-
开启事务管理:配置
@EnableTransactionManagement
🎯 调试技巧
1. 开启事务日志
properties
# application.yml logging.level.org.springframework.transaction=DEBUG logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
2. 查看代理是否生效
java
@Service
public class UserService {
@Transactional
public void update() { }
@PostConstruct
public void checkProxy() {
System.out.println("是否是代理对象: " + AopUtils.isAopProxy(this));
System.out.println("是否是CGLIB代理: " + AopUtils.isCglibProxy(this));
}
}
📚 参考资料
💬 欢迎在评论区补充你遇到过的其他事务失效场景!
📢 关注我,获取更多Spring实战干货!

5310

被折叠的 条评论
为什么被折叠?



