Spring事务流程源码剖析+传播行为运用案例+心得


总结:Spring的事务本质上就是操作数据库的事务

一、Spring事务流程源码剖析

Spring的声明式事务处理是通过AOP切面实现的核心逻辑在TransactionAspectSupport#invokeWithinTransaction中(这里不在赘述代理类如何生成以及切面何时进入,如有疑问可以参考SpringAOP之代理对象的创建SpringAOP之代理对象的执行Spring声明式事务控制原理之声明式事务的重要组件(注解实现)以及之间关系Spring声明式事务控制原理之声明式事务的重要组件在AOP中的应用

invokeWithinTransaction中三个主要步骤:
1.createTransactionIfNecessary 获取当前事务
2.completeTransactionAfterThrowing 发生异常时回滚
3.commitTransactionAfterReturning 无异常时提交

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {
    
    

		// If the transaction attribute is null, the method is non-transactional.
		// 获取事务属性解析器ProxyTransactionManagementConfiguration中注入
		TransactionAttributeSource tas = getTransactionAttributeSource();
		// 当前方法上声明式事务的相关属性信息
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		// 获取事务管理器,封装了事务启动、提交、回滚的横切逻辑
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		// 封装了当前方法相关信息
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
    
    
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			// 获取事务详情,开启事务(包含当前事务全部信息,以及上层挂起事务)
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

			Object retVal;
			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 {
    
    
				// 结合传播属性进行理解,处理挂起的事务
				cleanupTransactionInfo(txInfo);
			}
			// 如果目标方法正常执行执行事务提交操作。
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
    
    
			final ThrowableHolder throwableHolder = new ThrowableHolder();

			// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
			try {
    
    
				Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
    
    
					TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
					try {
    
    
						return invocation.proceedWithInvocation();
					}
					catch (Throwable ex) {
    
    
						if (txAttr.rollbackOn(ex)) {
    
    
							// A RuntimeException: will lead to a rollback.
							if (ex instanceof RuntimeException) {
    
    
								throw (RuntimeException) ex;
							}
							else {
    
    
								throw new ThrowableHolderException(ex);
							}
						}
						else {
    
    
							// A normal return value: will lead to a commit.
							throwableHolder.throwable = ex;
							return null;
						}
					}
					finally {
    
    
						cleanupTransactionInfo(txInfo);
					}
				});

				// Check result state: It might indicate a Throwable to rethrow.
				if (throwableHolder.throwable != null) {
    
    
					throw throwableHolder.throwable;
				}
				return result;
			}
			catch (ThrowableHolderException ex) {
    
    
				throw ex.getCause();
			}
			catch (TransactionSystemException ex2) {
    
    
				if (throwableHolder.throwable != null) {
    
    
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
					ex2.initApplicationException(throwableHolder.throwable);
				}
				throw ex2;
			}
			catch (Throwable ex2) {
    
    
				if (throwableHolder.throwable != null) {
    
    
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
				}
				throw ex2;
			}
		}
	}

1.createTransactionIfNecessary 获取当前事务流程分析

在这里插入图片描述

2.completeTransactionAfterThrowing 发生异常时回滚流程分析

在这里插入图片描述

3.commitTransactionAfterReturning 无异常时提交流程分析

在这里插入图片描述

二、传播行为运用案例

在这里插入图片描述

1.经典案例—回滚标记


		  out_method_required1{
    
    
		 
		      method_required_1(){
    
    
		      		保存数据
		      }
		 
		 
		 	try{
    
    
		      method_required_2(){
    
    
		     		保存数据
		          throw new RuntimeException;
		      }
		    }catch{
    
    
		        吃掉异常
		    }
		 
		  }

问:method_required_1中数据可以保存成功嘛?
	 
答:不会保存成功会一起回滚掉,因为method_required_2回滚时标记了ConnectionHolder为rollbackOnly = true;

2.经典案例2—REQUIRES_NEW传播行为运用


		  out_method_required2{
    
    
		     method_required_1(){
    
    
		     		保存数据
		     }
		 
		 
		 	try{
    
    
		      method_requires_new_2(){
    
    
		      		保存数据
		          throw new RuntimeException;
		       }
		    }catch{
    
    
		       吃掉异常
		    }
		 
		 }
		 

问:method_required_1会保存成功嘛?
答:会保存成功,因为method_requires_new_2事务的传播行为是REQUIRES_NEW类型,此类型的传播行为会挂起当前事务,重启一个新事务,与之前事务没有任何关系当新建事务执行完毕后在回复挂起的事务。通过操作TransactionSynchronizationManager中的ThreadLocal实现(数据库连接与线程的绑定关系)。

3.经典案例3—NESTED传播模式运用


		  out_method_required3{
    
    
		
		  try{
    
    
		     method_nested_2(){
    
    
		      		保存数据
		         throw new RuntimeException;
		      }
		   }catch{
    
    
		       吃掉异常
		    }
		 
		      method_required_1(){
    
    
		      		记录日志
		      }

		  }
		 
问:试问method_required_1会保存成功嘛?
答:会保存成功,因为method_nested_2的传播类型是NESTED类型,在触发回滚时会利用保存点进行回滚+清除保存点,不会标记ConnectionHolder为只能回滚状态导致使用当前连接的全部事务回滚;在触发Commit时会清除保存点。故不会导致全局回滚。

4.经典案例4—NESTED传播模式运用2

 		out_method_required3{
    
    
		  method_nested_1(){
    
    
		      		记录日志
		    }
		 
		  try{
    
    
		     method_required_2(){
    
    
		     		保存数据
		         throw new RuntimeException;
		       }
		   }catch{
    
    
		       吃掉异常
		   }
		 }
问:method_nested_1会保存成功嘛?
答:不会保存成功,method_nested_1采用NESTED的传播模式,当前模式下commit操作会清除保存点,但不会真正操作Commit,会跟随外层事务一起提交回滚,method_required_2采用的是REQUIRED传播模式,发生回滚是会标记ConnectionHolder为只能回滚状态,导致外层事务也会回滚从而导致method_nested_1回滚保存失败。

三、心得

在内层事务的视角解读三种传播行为:

1.REQUIRED是成功了与外层事务一起Commit或着一起Rollback,失败了标记使用当前连接的事务未RollbackOnly=true状态,会导致使用当前Connection连接的事一起回滚包括外层事务。
2.REQUIRES_NEW是挂起外层事务,Commit和Rollback都与外层事务无关,只自己提交或者回滚,当回滚或提交结束后会回复外层挂起事务;
3.NESTED是成功了与外层事务一起Commit或着一起Rollback,失败了自己回滚不影响外层事务;

NESTD和REQUIRES_NEW运用场景

如果存在"业务逻辑成功需要记录日志,失败日志一起回滚,且日志记录异常不能影响业务逻辑"这种需求。
建议业务逻辑使用REQUIRED传播行为放在前面,记录日志使用NESTED传播行为放在后面。
不建议使用REQUIRES_NEW这种传播行为,因为当执行REQUIRES_NEW传播行为的事务横切逻辑时会多开一个Connection连接,比起NESTED更占用资源。

猜你喜欢

转载自blog.csdn.net/yangxiaofei_java/article/details/113634986
今日推荐