从源码级别定位事务失效

223 阅读3分钟

1、从源码级别定位事务失效

我们知道,Spring 是通过 Spring AOP 来达到事务的回滚操作的,而 Spring AOP 又是通过动态代理实现的,这时候我们知道了事务有效的几个条件,第一是有可以执行的增强器链,也就是类似于我们平时用的各种通知,在 Spring 事务中定义了一个默认的增强器 TransactionInterceptor ,这个类在 @EnableTransactionManagement 注解中的 ProxyTransactionManagementConfiguration 被注册。第二是类能被动态代理。下面我们定位到具体的类中来分析问题。

1.1、事务类不能被代理

事务类是否能被创建代理,关键在于 AbstractAutoProxyCreator 类的 postProcessAfterInitialization 方法:

@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 方法

if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
				}
}

DefaultTransactionAttribute 的 rollbackOn 方法:

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 会怎么样?

//我们知道这个是那运行产生的异常去与规则对比的,如果规则是 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);
	}

前置知识补充 Spring 事务原理