Spring事务处理机制以及错误使用TransactionSynchronization的afterCompletion方法引起的问题

本文深入探讨了Spring的事务处理机制,包括声明式和编程式事务的使用。作者在代码重构过程中遇到一个数据库连接关闭的异常问题,通过分析Spring 4.2.4.RELEASE的源码,发现问题是由于在`TransactionSynchronization`的`afterCompletion`方法中错误地操作了Dao层,此时事务虽已完成但资源未释放。文章提醒开发者注意在事务完成后执行数据库操作的风险,并建议遵循Spring的事务管理规范。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

我们都知道spring有声明式事务和编程式事务,声明式只需要提供@Transactional的注解,然后事务的开启和提交/回滚、资源的清理就都由spring来管控,我们只需要关注业务代码即可;而编程式事务则需要使用spring提供的模板,如TransactionTemplate,或者直接使用底层的PlatformTransactionManager。

声明式事务的最大优点就是对代码的侵入性较小,只需要在方法上加@Transactional的注解就可以实现事务;编程式事务的最大优点就是事务的管控粒度较细,在实现某个代码块的事务。

背景

简单介绍完spring的事务机制那就要引入这一次碰到的问题了,我相信大多数人应该和我一样,只要怎么使用,比如加个注解啥的,但是底层原理不清楚。好一点的知道AOP动态代理,再好一点就是知道事务的传播机制(一般也就用用默认的REQUIRED)。真正底层的事务处理的源码很多人应该是没有看过的,当然我也是没有滴~~ 但是这一次碰到的问题让我不得不去看源码了。

这段时间一直在做代码的重构,既然是重新写的代码,当然想写得漂亮一点,不然是要被后人戳脊梁骨的~~ 结果所有代码都写完,都提测了,在测试环境却报一个诡异的异常

java.sql.SQLException: PooledConnection has already been closed

而且这不是必现的,而一旦出现,那任何涉及数据库连接的接口都有可能报这个错。从字面意思看是用到的数据库连接被关闭了,但是理解不能啊,这种底层的事情不都是spring帮忙做好了么。建议测试重启机器,心中期待不会再现

结果依然出现,而且频率还不低,都阻塞测试了。。那就只好操起久违的调试源码的大刀,硬着头皮上了。

过程

本次源码使用的是spring版本是 4.2.4.RELEASE,事务管理器则是参照项目使用的DataSourceTransactionManager。

入口
  1. 首先是事务的入口,spring用的是动态代理,如果某个方法被标注了@Transactional,则会由TransactionInterceptor拦截,在原始方法的前后增加一些额外的处理。可以看到,调用的是TransactionInterceptor的invoke方法,而内部又调用了invokeWithinTransaction方法,但其实这个并不一定会创建事务(事务传播机制里有几种情况是不需要或者不支持事务的)。
public Object invoke(final MethodInvocation invocation) throws Throwable {
	// Work out the target class: may be {@code null}.
	// The TransactionAttributeSource should be passed the target class
	// as well as the method, which may be from an interface.
	Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

	// Adapt to TransactionAspectSupport's invokeWithinTransaction...
	return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
		@Override
		public Object proceedWithInvocation() throws Throwable {
			return invocation.proceed();
		}
	});
}
  1. TransactionInterceptor继承了TransactionAspectSupport这个抽象类,invokeWithinTransaction这个方法是在父类中的。方法里的英文注释是源码中的,中文注释是我加上去的。CallbackPreferringPlatformTransactionManager是实现了PlatformTransactionManager接口,如果使用的事务管理器是CallbackPreferringPlatformTransactionManager的实现,则会将事务的控制交由这个类的execute方法,这里先省略。我们来看一般情况(很多应该用的是DataSourceTransactionManager吧),总的来说可以将这个方法分为几部分:
    在这里插入图片描述
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
	// If the transaction attribute is null, the method is non-transactional.
	final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
	final PlatformTransactionManager tm = determineTransactionManager(txAttr);
	final String joinpointIdentification = methodIdentification(method, targetClass);

	if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
		// Standard transaction demarcation with getTransaction and commit/rollback calls.
		// 如果当前方法需要事务则会创建事务(@Transactional不代表一定创建事务,可以看spring的事务传播机制)
		TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
		Object retVal = null;
		try {
			// This is an around advice: Invoke the next interceptor in the chain.
			// This will normally result in a target object being invoked.
			// 可以将这个方法视为调用真正的业务方法(其实内部还有一些拦截器的处理)
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			// target invocation exception
			// 事务抛出异常的时候的处理
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
		}
		finally {
		    // 只做一件事,就是把事务的上下文信息改回本事务开始之前的上下文
		    // 因为有可能本事务是被包裹在其他事务中的,可以看spring的事务传播机制
			cleanupTransactionInfo(txInfo);
		}
		// 事务执行成功后将事务的状态信息提交
		commitTransactionAfterReturning(txInfo);
		return retVal;
	}

	else {
		// It's a CallbackPreferringPlatformTransactionManager: pass a 
`TransactionSynchronizationManager.registerSynchronization` 是 Spring 中用于在事务管理上下文的 commit 或 rollback 阶段注册同步器的功能。通常,这是为了确保在事务完成时执行特定的操作,例如清理资源、更新缓存等。 当在一个事务的 `afterCommit` 下游阶段抛出异常时,如果此时该阶段的同步器尝试进行一些操作,并引发异常,则可能导致当前事务无法正常结束,进而影响后续的处理结果。例如,在事务提交之后调用了某个依赖于事务状态的操作,但是由于异常导致了事务状态未能如预期那样进行处理。 解决这类问题的一个策略是在 `afterCompletion` 同步器中设置适当的回滚逻辑,而不是仅仅依赖于异常直接导致的默认行为。Spring 提供了多种机制来处理这种场景: ### 解决方案 #### 1. **使用 TransactionTemplate 或 @Transactional 注解** - 如果你的应用中有大量涉及到事务控制的地方,可以考虑使用 `TransactionTemplate` 或 `@Transactional` 注解。这些工具内置了一些错误处理逻辑,能够更优雅地应对异常情况,避免不必要的提交或回滚。 ```java @Autowired private PlatformTransactionManager transactionManager; public void someMethod() { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { // 执行业务逻辑... } catch (Exception e) { transactionManager.rollback(status); // 可以选择记录日志或其他错误处理步骤 } } ``` #### 2. **在 `afterCompletion` 中添加逻辑** - 如果你仍然需要自定义在事务提交或回滚时的行为,可以在注册同步器时,通过 `TransactionSynchronizationAdapter` 来修改默认行为。 ```java public class CustomSynchronization implements TransactionSynchronization { public void afterCommit() throws RollbackException { try { // 这里插入你希望在事务成功后执行的代码块 } catch (Exception e) { throw new RuntimeException("Error occurred in afterCommit", e); } } public void afterCompletion(int flag) { try { // 这里插入你希望在事务成功或失败后执行的代码块 } catch (Exception e) { throw new RuntimeException("Error occurred in afterCompletion", e); } } } // 将自定义同步器注册到事务管理器中 TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.class, new WeakReference<>(new CustomSynchronization())); ``` #### 3. **增加异常处理机制** - 确保在业务逻辑层添加足够的异常捕获和处理机制,特别是对于那些依赖于事务状态的操作,应该包含异常处理流程,防止意外情况导致的系统级问题。 ### 相关问题: 1. 如何利用 TransactionTemplate 更有效地管理事务? 2.Spring Boot 应用中如何配置默认的 TransactionManager? 3.使用 `@Transactional` 注解时应考虑哪些最佳实践?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值