1、从源码级别定位事务失效
我们知道,Spring 是通过 Spring AOP 来达到事务的回滚操作的,而 Spring AOP 又是通过动态代理实现的,这时候我们知道了事务有效的几个条件,第一是有可以执行的增强器链,也就是类似于我们平时用的各种通知,在 Spring 事务中定义了一个默认的增强器 TransactionInterceptor ,这个类在 @EnableTransactionManagement
注解中的 ProxyTransactionManagementConfiguration 被注册。第二是类能被动态代理。下面我们定位到具体的类中来分析问题。
1.1、事务类不能被代理
事务类是否能被创建代理,关键在于 AbstractAutoProxyCreator 类的 postProcessAfterInitialization 方法:
代码语言:less复制@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
如果 Bean 没有走到这里说明当前 Bean 不能被动态代理,通常控制台会打印一条语句 is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
。这条信息由 PostProcessorRegistrationDelegate 下的 BeanPostProcessorChecker 的 postProcessAfterInitialization 打印出来的,为什么会打印这个呢?里面的注释已经明确说明了:
BeanPostProcessor that logs an info message when a bean is created during
BeanPostProcessor instantiation, i.e. when a bean is not eligible for
getting processed by all BeanPostProcessors.
大概的意思就是 Bean 在 BeanPostProcessor 初始化的时候被提前初始化了,因为这个是用来处理一些普通 bean 添加一些信息的,Bean 都先于它加载了,那么自然也就处理不到它了,最常见的地方就是 Shiro 配置中提前引用了普通 Bean 导致后续不能被代理,进而导致事务失效。
1.2、类没有符合条件的增强器
我们定位到事务的增强器中,TransactionInterceptor 的 invoke 方法,然后到具体的处理细节 TransactionAspectSupport 的 invokeWithinTransaction 方法中,由这个方法去执行事务的回滚,如果 debug 到这里发现方法没有执行到这,那么事务也是失效的,最常见的就是在方法上没有添加事务注解,自然的当前类就不符合事务通知的拦截点了,增强器也就不会执行。
下面列举几种常见的事务失效原因:
2、Spring 事务失效原因
2.1、未加 Transactional 注解
2.2、方法类未被加入到 IOC 容器中
整个事务环境都是依赖于 IOC 容器的,类不在咋处理。
2.3、方法产生的异常不是 RuntimeException
我们将目光定位到以下代理,TransactionAspectSupport 的 completeTransactionAfterThrowing 方法
代码语言:scss复制if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
}
DefaultTransactionAttribute 的 rollbackOn 方法:
代码语言:typescript复制public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
2.4、类被提前初始化
如果控制台打印了 is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
那么要注意这个 Bean 了,因为它不能被动态代理了,处理办法就是将这个 Bean 在一些特殊类中移出来,比如配置类。
3、Spring 传播行为的处理
具体处理在 AbstractPlatformTransactionManager 的 AbstractPlatformTransactionManager 方法处理。
4、Spring 隔离级别
我们知道 Spring 对于隔离级别都是委托底层的数据库去处理的,所以在 Spring 中的具体处理细节就是设置一下隔离级别,具体在 AbstractPlatformTransactionManager 的 getTransaction 方法的 doBegin(transaction, definition);
里面,可以找一个实现类去看一下。
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
5、Transactional 注解中 rollbackFor 的作用
如果在注解中配置了 rollbackFor 属性,那么在走到 txInfo.transactionAttribute.rollbackOn(ex)
这里的时候,它会进 RuleBasedTransactionAttribute 的 rollbackOn 方法:
public boolean rollbackOn(Throwable ex) {
if (logger.isTraceEnabled()) {
logger.trace("Applying rules to determine whether transaction should rollback on " ex);
}
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
// 如果配置了 rollbackFor 那么这里就是有回滚规则的。
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
// 这里通过比较异常的名称是否相等,不相等则取抛出的异常的父类与定义的匹配规则比较,同时深度也加一,直到匹配出异常名称,如果最后匹配到
//Throwable异常,那就直接返回-1了,winner也为null
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Winning rollback rule is: " winner);
}
// 没有规则就直接调用父类的回滚了不会走下去
// User superclass behavior (rollback on unchecked) if no rule matches.
if (winner == null) {
logger.trace("No relevant rollback rule found: applying default rules");
return super.rollbackOn(ex);
}
return !(winner instanceof NoRollbackRuleAttribute);
}
那我们配置成 Throwable 会怎么样?
代码语言:text复制//我们知道这个是那运行产生的异常去与规则对比的,如果规则是 Throwable 那么会匹配成功返回正数,如果没匹配到的话就会走到-1那里。
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);
}
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!