写完代码又一次习惯性的在方法上加了 Transactional
注解来开启事务的处理,但是本次我突然发现 居然失效了!!!接下来说一下的事情的经过。
1. 事发现场
@Transactional(rollbackFor = Exception.class)
public void someMethod() {
for (...) {
try {
xxxMapper.insert(item);
findItemConfig(Long.parseLong(item.getBenchmarkRelId()),item.getId());
} catch (Exception e) {
throw new RuntimeException("Find standard item config failed: "+e.getMessage()e);
}
}
}
2.事故描述,分析原因
当findItemConfig方法发生异常的时候, xxxMapper.insert(item);居然正常执行,并没有发生回滚,然后我陷入了自我怀疑当中:难道之前一贯的用法时错误的?之后又手动造了几个异常,发现依然不回滚。沉思一番,快速扫描了一遍写的代码,注解正常,那就只有一个原因了。嗯......好吧,我承认在代码上有些失误。
是因为在for循环中的缘故,在循环内部无法触发 @Transactional
注解的回滚是因为事务的控制粒度问题。通常情况下,Spring 会通过 AOP(面向切面编程)来实现 @Transactional
的功能。当一个方法被调用时,Spring 会在其周围创建一个事务边界,并在方法执行前后进行事务的开启、提交或回滚等操作。然而,在循环内部,每次迭代都会执行一次循环体代码块,这些迭代往往不会被 Spring 感知到,因此事务边界无法正确地包含多个迭代操作。解决这个问题有两个方法,声明性事务(@Transactional
注解),编程事务来实现更细粒度的事务控制。
3.解决方法
声明性事务:
@Transactional(rollbackFor = Exception.class)
public void someMethod() {
for (...) {
// 循环体代码块
doSomething();
}
}
@Transactional(rollbackFor = Exception.class)
public void doSomething() {
// 执行具体的操作
}
编程事务:
public void someMethod() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus status = transactionManager.getTransaction(def);
try {
for (...) {
// 循环体代码块
}
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}