Spring事务失效的常见原因
Spring 事务失效的常见原因
Spring 事务管理(@Transactional
)是开发中常用的功能,但在某些情况下事务可能失效。以下是常见的事务失效原因、示例及对应的解决方案。
1. 方法访问权限问题(非 public 方法)
原因:Spring为方法创建代理、添加事务通知前提条件都是该方法是public的,Spring AOP 代理机制要求 @Transactional
只能应用于 public
方法,否则事务不会生效。
示例:
@Service
public class UserService {@Transactionalprivate void createUser(User user) { // 事务失效userDao.save(user);}
}
解决方案:
✅ 确保 @Transactional
方法为 public
@Service
public class UserService {@Transactionalpublic void createUser(User user) { // 事务生效userDao.save(user);}
}
2. 自调用问题(同一个类内部调用)
原因:Spring 事务基于 AOP 代理,自调用时不会经过代理类,导致事务失效。
示例:
@Service
public class OrderService {public void placeOrder(Order order) {// 其他逻辑this.saveOrder(order); // 自调用,事务失效}@Transactionalpublic void saveOrder(Order order) {orderDao.save(order);}
}
解决方案:
✅ 方法1:拆分到不同类
@Service
public class OrderService {@Autowiredprivate OrderTransactionService orderTransactionService;public void placeOrder(Order order) {orderTransactionService.saveOrder(order); // 代理生效}
}@Service
public class OrderTransactionService {@Transactionalpublic void saveOrder(Order order) {orderDao.save(order);}
}
✅ 方法2:使用 AopContext.currentProxy()
(需开启 @EnableAspectJAutoProxy(exposeProxy = true)
)
@Service
public class OrderService {public void placeOrder(Order order) {((OrderService) AopContext.currentProxy()).saveOrder(order); // 代理调用}@Transactionalpublic void saveOrder(Order order) {orderDao.save(order);}
}
3. 异常被捕获未抛出
原因:默认情况下,Spring 事务只在抛出 RuntimeException
或 Error
时回滚。如果异常被 catch
但没有重新抛出,事务不会回滚。
示例:
@Service
public class AccountService {@Transactionalpublic void transfer(Account from, Account to, BigDecimal amount) {try {accountDao.deduct(from, amount);accountDao.add(to, amount);} catch (Exception e) {log.error("转账失败", e); // 异常被捕获,事务不回滚}}
}
解决方案:
✅ 方法1:重新抛出异常
catch (Exception e) {log.error("转账失败", e);throw new RuntimeException(e); // 触发回滚
}
✅ 方法2:指定 @Transactional(rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class) // 任何异常都回滚
public void transfer(Account from, Account to, BigDecimal amount) {// ...
}
4. 数据库引擎不支持事务(如 MySQL 的 MyISAM)
原因:某些数据库引擎(如 MyISAM)不支持事务,即使加了 @Transactional
也不会生效。
解决方案:
✅ 确保使用支持事务的引擎(如 InnoDB)
ALTER TABLE your_table ENGINE=InnoDB;
5. 事务传播行为设置不当
原因:如果传播行为设置为 Propagation.NOT_SUPPORTED
或 Propagation.NEVER
,事务不会生效。
示例:
@Transactional(propagation = Propagation.NOT_SUPPORTED) // 不支持事务
public void logOperation(Log log) {logDao.save(log); // 无事务
}
解决方案:
✅ 合理设置传播行为(默认 REQUIRED
)
@Transactional(propagation = Propagation.REQUIRED) // 默认值,支持事务
public void logOperation(Log log) {logDao.save(log);
}
6. 类未被 Spring 管理
原因:如果类没有 @Service
、@Component
等注解,@Transactional
不会生效。
示例:
public class ExternalService { // 没有 @Service/@Component@Transactional // 无效public void process() {// ...}
}
解决方案:
✅ 确保类被 Spring 管理
@Service
public class ExternalService {@Transactional // 生效public void process() {// ...}
}
7. 多数据源未正确配置事务管理器
原因:多数据源环境下,如果没有为每个数据源配置事务管理器,事务可能失效。
解决方案:
✅ 配置多数据源事务管理器
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {@Bean@Primarypublic PlatformTransactionManager primaryTransactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Beanpublic PlatformTransactionManager secondaryTransactionManager(DataSource secondaryDataSource) {return new DataSourceTransactionManager(secondaryDataSource);}
}
使用时指定事务管理器:
@Transactional("primaryTransactionManager") // 指定事务管理器
public void primaryDbOperation() {// ...
}
8. 异常类型不匹配
原因:默认情况下,Spring事务只对RuntimeException和Error回滚,对受检异常(checked exception)不回滚。那什么是受检异常?受检异常(Checked Exceptions)是那些在编译时就必须被处理的异常,它们继承自Exception类但不继承RuntimeException类。以下是Java中常见的受检异常:
- IO相关异常
IOException:输入输出操作失败的通用异常
FileNotFoundException:试图打开不存在的文件
EOFException:在输入过程中意外到达文件或流的末尾
SocketException:底层协议有错误,如TCP错误
MalformedURLException:URL格式不正确 - SQL相关异常
SQLException:数据库访问错误或其他错误
SQLTimeoutException:数据库操作超时
SQLSyntaxErrorException:SQL语法错误 - 反射相关异常
ClassNotFoundException:无法加载请求的类
NoSuchMethodException:请求的方法不存在
IllegalAccessException:对类、方法或字段的访问被拒绝 - 网络相关异常
UnknownHostException:无法确定主机的IP地址
ConnectException:连接服务器被拒绝 - 其他常见受检异常
ParseException:解析字符串失败(如日期解析)
CloneNotSupportedException:对象不支持克隆操作
InterruptedException:线程被中断
TimeoutException:阻塞操作超时
解决方法也很简单,添加rullbackFor: @Transactional(rollbackFor=Exception.class)
示例:
@Service
public class FileService {@Transactionalpublic void processFile() throws IOException { // 受检异常// 文件处理逻辑throw new IOException("文件处理失败"); // 默认情况下不会导致事务回滚}
}
受检异常 vs 非受检异常
继承关系: 受检异常继承Exception但不继承RuntimeException,非受检异常继承RuntimeException或Error
处理要求: 受检异常必须捕获或声明抛出, 非受检异常不强制要求处理
示例: 受检异常IOException, SQLException,非受检异常NullPointerException, ArrayIndexOutOfBoundsException
使用场景: 受检异常可恢复的条件,调用者应该处理的情况,非受检异常程序错误,通常不应捕获
总结
失效原因 | 解决方案 |
---|---|
方法非 public | 改为 public 方法 |
自调用问题 | 拆分到不同类或使用 AopContext.currentProxy() |
异常被捕获未抛出 | 重新抛出异常或配置 rollbackFor |
数据库引擎不支持事务 | 使用 InnoDB 等支持事务的引擎 |
传播行为设置不当 | 使用 REQUIRED (默认) |
类未被 Spring 管理 | 添加 @Service /@Component |
多数据源未配置事务管理器 | 为每个数据源配置 PlatformTransactionManager |
异常类型不匹配 | 添加 rollbackFor=Exception.class |