出错原因
同一个代理类里面,被调用方法有@Transactional注解,而外层防范没有注解
@Override
public void test1() {
test2();
}
@Transactional
public void test2(){
try {
... sql操作 ...
System.out.println(1/0) ; //抛出异常
} catch (Exception e) {
// 发生异常时手动触发事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
在上述代码中事务不仅不会回滚,还会报出异常org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
原因解释
事实上Spring中的@Transactional原理是通过TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截。像上面的代码,Spring AOP实际上是为每个@Service注解的类生成一个代理类,由于代理类的test1方法没有被注解修饰,Spring认为这里不用事务拦截器进行拦截,即便被调用类中使用了事务注解。
使用注意事项
- 在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。
- @Transactional 注解应该只被应用在 public 修饰的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 该注解,它也不会报错(IDEA会有提示), 但事务并没有生效。
a. 被外部调用的公共方法A有两个进行了数据操作的子方法B和子方法C的事务注解说明:
b.被外部调用的公共方法A未声明事务@Transactional,子方法B和C若是其他类的方法且各自声明事务,则事务由子方法B和C各自控制
c.被外部调用的公共方法A未声明事务@Transactional,子方法B和C若是本类的方法,则无论子方法B和C是否声明事务,事务均不会生效
d.被外部调用的公共方法A声明事务@Transactional,无论子方法B和C是不是本类的方法,无论子方法B和C是否声明事务,事务均由公共方法A控制
被外部调用的公共方法A声明事务@Transactional,子方法运行异常,但运行异常被子方法自己 try-catch 处理了,则事务回滚是不会生效的!
如果想要事务回滚生效,需要将子方法的事务控制交给调用的方法来处理:
方案1:子方法中不用 try-catch 处理运行异常
方案2:子方法的catch里面将运行异常抛出【throw new RuntimeException();】
默认情况下,Spring会对unchecked异常进行事务回滚,也就是默认对 RuntimeException() 异常或是其子类进行事务回滚。
如果是checked异常则不回滚,例如空指针异常、算数异常等会被回滚;文件读写、网络问题Spring就没法回滚。
若想对所有异常(包括自定义异常)都起作用,注解上面需配置异常类型:@Transactional(rollbackFor = Exception.class)
数据库要支持事务,如果是mysql,要使用innodb引擎,myisam不支持事务
事务@Transactional由spring控制时,它会在抛出异常的时候进行回滚。如果自己使用try-catch捕获处理了,是不生效的。如果想事务生效可以进行手动回滚或者在catch里面将异常抛出【throw new RuntimeException();】
方案一:手动抛出运行时异常(缺陷是不能在catch代码块自定义返回值)
try{
....
}catch(Exception e){
logger.error("",e);
throw new RuntimeException(e);
}
方案二:手动进行回滚【 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 】
try{
...
}catch(Exception e){
log.error("fail",e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return false;
}
- @Transactional可以放在Controller下面直接起作用,并非必须要放到@Component下面或者@Service下面
- @Transactional引入包问题,它有两个包:
import javax.transaction.Transactional;
// 和
import org.springframework.transaction.annotation.Transactional; // 推荐
这两个都可以用,对比了一下他们两个的方法和属性,发现后面的比前面的强大。建议使用后面的。