Spring使用@Transactional注解,事务失效场景
1、@Transactional 应用在非 public 修饰的方法上
如果 Transactional
注解应用在非 public
修饰的方法上,Transactional将会失效。
之所以会失效是因为在Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute`方法,获取Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Methodmethod,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() &&!Modifier.isPublic(method.getModifiers())) {
return null;
}
Modifier.isPublic会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
注意: protected
、 private
修饰的方法上使用 @Transactional
注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。
2、数据库引擎要不支持事务
数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的。
3、@由于propagation 设置错误,导致注解失效
propagation 属性我们知道
TransactionDefinition.PROPAGATION_SUPPORTS
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER
以非事务方式运行,如果当前存在事务,则抛出异常。
当我们将propagation 属性设置为上述三种时,@Transactional 注解就不会产生效果
4、rollbackFor 设置错误,@Transactional 注解失效
上述我们解读rollbackFor 属性的时候我们知道
rollbackFor
可以指定能够触发事务回滚的异常类型。
Spring默认抛出了未检查 unchecked
异常(继承自 RuntimeException
的异常)或者 Error
才回滚事务;
其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor 属性。
// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor=MyException.class
若在目标方法中抛出的异常是 rollbackFor
指定的异常的子类,事务同样会回滚。Spring源码如下:
private int getDepth(Class<?> exceptionClass, int depth) {
if (exceptionClass.getName().contains(this.exceptionName)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass == Throwable.class) {
return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}
5、方法之间的互相调用也会导致@Transactional失效
我们来看下面的场景:
比如有一个类User,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
那为啥会出现这种情况?其实这还是由于使用 Spring AOP
代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由 Spring
生成的代理对象来管理。
//@Transactional
@GetMapping("/user")
private Integer A() throws Exception {
User user = new User();
user.setName("javaHuang");
/**
* B 插入字段为 topJavaer的数据
*/
this.insertB();
/**
* A 插入字段为 2的数据
*/
int insert = userMapper.insert(user);
return insert;
}
@Transactional()
public Integer insertB() throws Exception {
User user = new User();
user.setName("topJavaer");
return userMapper.insert(user);
}
6、异常被你的 catch“吃了”导致@Transactional失效
这种情况是最常见的一种@Transactional注解失效场景,
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
User user = new User();
user.setCityName("javaHuang");
user.setUserId(1);
/**
* A 插入字段为 javaHuang的数据
*/
insert = userMapper.insert(user);
/**
* B 插入字段为 topJavaer的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务就不能正常回滚,而是会报出异常
org.springframework.transaction.UnexpectedRollbackException: Transactionrolled back because it has been marked as rollback-only
解决方法:
第一声明事务的时候加上rollback=‘exception’
第二 cath代码块里面手动回滚
总结
@Transactional 注解我们经常使用,但是往往我们也只是知道它是一个事务注解,很多时候遇到事务注解失效的情况下,我们都是一头雾水,看不出个所以然来,花费了很长的时间都不能解决。
通过本文了解了@Transactional 注解的失效场景,在以后遇到这种情况时,基本就能一眼看破,然后摸摸自己光滑的脑门,soga,so easy!
妈妈再也不用担心我找不到自己写的bug了。