Spring事务回滚失效:@Transactional标注私有方法抛异常不回滚
问题根因
事务不生效有两个核心原因:
- 私有方法不支持@Transactional:Spring声明式事务基于AOP动态代理实现,只有public修饰的外部可访问方法才会被代理对象增强,private方法不会被代理逻辑拦截,加了@Transactional相当于无效注解。
- 同类内部调用绕过代理对象:就算你把
getNameString改成public,你现在是在同一个ExpenseService类的testBatch方法里直接调用本类的getNameString,本质是this.getNameString(),调用的是原对象而非代理对象,也不会触发事务切面逻辑。
解决方案
根据你的业务需求(每个批次独立事务,单个批次出错仅回滚当前批次数据),可以选择以下任意一种方案:
方案1:通过代理对象调用本类方法
方式A:通过AOP上下文获取代理
- 启动类添加
@EnableAspectJAutoProxy(exposeProxy = true)注解,开启代理对象暴露功能 - 将
getNameString的修饰符从private改为public - 调整
testBatch方法中的调用逻辑:
// 调整后的testBatch方法调用部分 try { ((ExpenseService) AopContext.currentProxy()).getNameString(expenses, start, end); } catch (Exception ex) { System.out.println("Exception occured"); }
方式B:自己注入自己获取代理
不需要修改启动类配置,直接在类内注入代理对象:
@Service public class ExpenseService { // Spring会注入当前类的代理对象 @Autowired private ExpenseService self; // 其余原有属性不变 public void testBatch() { // ... 原有逻辑不变 try { self.getNameString(expenses, start, end); } catch (Exception ex) { System.out.println("Exception occured"); } } // 将getNameString改为public @Transactional(rollbackFor = {RuntimeException.class}) public void getNameString(List<Expense> expenses, int start, int end) throws RuntimeException { // 原有逻辑不变 } }
方案2:将事务逻辑抽到独立Service
将批次处理的事务逻辑单独拆分到一个新的Service类中,彻底避免同类内部调用问题:
@Service public class ExpenseBatchService { @Autowired private ExpenseRepository expenseRepository; @Transactional(rollbackFor = RuntimeException.class) public void processBatch(List<Expense> expenses, int start, int end) { List<Expense> test = new ArrayList<>(); for (int i = start; i < end; i++) { Expense temp = expenses.get(i); temp.setItem(temp.getItem() + i); test.add(temp); } expenseRepository.saveAll(test); throw new RuntimeException("Timeout"); } }
然后在ExpenseService中注入这个新的Service调用即可:
@Service public class ExpenseService { @Autowired private ExpenseRepository expenseRepository; @Autowired private ExpenseBatchService batchService; // 其余原有方法不变 public void testBatch() { List<Expense> expenses = expenseRepository.findAll(); int batchSize = 5; for (int i = 0; i < expenses.size(); i++) { int start = i; int end = Math.min(i + batchSize, expenses.size() - 1); i = end; try { batchService.processBatch(expenses, start, end); } catch (Exception ex) { System.out.println("Exception occured"); } } } }
方案3:整批统一事务
如果你不需要每个批次独立事务,希望所有批次操作要么全部成功要么全部回滚,直接把@Transactional注解放到testBatch方法上即可,不需要调整其他逻辑。
补充说明:你在
testBatch中捕获异常的操作不影响事务回滚,只要异常从被@Transactional修饰的方法中抛出,事务切面已经接收到异常会自动标记回滚,上层捕获异常仅用于业务告警/降级,不会影响回滚逻辑。
内容的提问来源于stack exchange,提问作者Nitin Sharma
相关产品推荐
相关产品推荐

