Spring事务详解与避坑指南

事务介绍

1.1 什么是事务

事务在逻辑上是一组操作,要么都执行,要不都不执行。
为了保证事务是正确可靠的,在数据库进行写入或者更新操作时,就必须得表现出 ACID 的 4 个重要特性:

  1. 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么全部执行失败;
  2. 一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
  3. 隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
  4. 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

1.2 事务执行流程:

1.3 事务的使用

Spring支持两种类型的事务管理:
编程式事务管理:通过编程的方式管理事务也叫侵入性事务管理,带来极大的灵活性,但是难维护。实现方式是使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
**声明式事务管理:**包含两种配置方式,第一种是使用 XML 进行模糊匹配,绑定事务管理;第二种是使用注解,这种方式可以对每个需要进行事务处理的方法进行单独配置,你只需要添加上@Transactional,然后在注解内添加属性配置即可。
代码示例:

@Transactional
public void saveEmployeeInfo(EmployeeHire request) {
    //CRUD
}

1.4 事务隔离级别

Spring事务有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:

隔离级别含义脏读不可重复读幻读
ISOLATION_DEFAULT默认使用数据库隔离级别,数据库设置的是什么就用什么;---
ISOLATION_READ_UNCOMMITTED未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
ISOLATION_READ_COMMITTED提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读);×
ISOLATION_REPEATABLE_READ可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;××
ISOLATION_SERIALIZABLE序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。×××

1.5 事务传播行为

事务传播行为是指在已有事务中调用其它启用事务的方法时发生的事务传播机制。

传播级别序号含义
PROPAGATION_REQUIRED0如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,默认是此机制。
PROPAGATION_SUPPORTS1支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
PROPAGATION_MANDATORY2支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
PROPAGATION_REQUIRES_NEW3创建新事务,无论当前存不存在事务,都创建新事务。
PROPAGATION_NOT_SUPPORTED4以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER5以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED6如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

1.6 Spring声明式事务配置参数

@Transactional()
image.png

  • value/transactionManager�**: 指定事务管理器,默认使用PlatformTransactionManager�。
    小知识-
    @AlisaFor**image.png
    @AliasFor注解用来指定别名,这里的意思是value和transactionManager互为别名。
    也就是value的别名是transactionManager�,而transactionManager�的别名是value。
    我们通过给value属性Set值,通过transactionManager�也可以获取到value属性的值。

  • **propagation�: **事务传播机制,默认以加入当前事务方式处理。

  • **isolation:**事务隔离机制,默认使用底层事务的隔离级别。

  • timeout�:超时时间的配置,默认使用底层事务的超时时间(如使用MySQL数据库,则按照MySQL的超时时间处理)。

  • readOnly�:是否只读,如果是true将不允许在事务内进行写入的操作,此配置常用于保证读一致性的场景,默认false。

  • rollbackFor�:指定回滚异常类,指定哪些异常类型才会进行回滚操作,默认是RuntimeException和Error。

  • rollbackForClassName�:根据类全限定名指定回滚异常类。

  • noRollbackFor�:指定哪些异常类型不会被回滚,默认为空。

  • noRollbackForClassName�:根据类全限定名指定哪些异常类型不会被回滚。

避坑指南

声明类:

2.1 不可以用private

私有的方法类不会被代理

@Transactional
private void create() {
    ...
}

2.2 事务方法未被Spring管理

没有通过SpringIOC容器管理的事务方法类无法回滚。

2.3 不可以本类this调用

本类中this调用时没有走Spring的AOP容器,所以通过this调用的事务方法不会回滚。
错误的本类调用:

public void main(){
    save();
}

@Transactional
public void save() {
  ...
}

正确的本类调用

@Resource
TestService self;
    
public void main(){
    self.save();
}

@Transactional
public void save() {
  ...
}

2.4 事务传播机制不支持事务

使用了不以事务方式执行的的事务传播机制,例如以下三种事务传播机制

传播级别序号含义
PROPAGATION_SUPPORTS1支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
PROPAGATION_NOT_SUPPORTED4以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER5以非事务方式执行,如果当前存在事务,则抛出异常。

2.5 多数据源情况下未指定事务管理器

当一个应用存在多个事务管理器时,如果不指定事务管理器,@Transactional会按照事务管理器在配置文件中的初始化顺序使用其中一个。
如果存在多个数据源datasource1和datasource2,假设默认使用datasource1的事务管理器,当对datasource2进行数据操作时就处于非事务环境。
解决办法是,可以通过@Transactional的value属性指定一个事务管理器。

2.6 Exception类型异常需要指定

原因在于默认捕获的是RuntimeException和Error
image.png

2.7 不正确的异常处理

如果在事务处理类内用了try/catch块,那必须要把异常throw出去,不要在方法内“吞”掉。
错误的异常处理

@Transactional
public void save() {
  try{
     ...
  } catch (Exception e) {
    log.error("处理异常...");
  }
}

正确的异常处理

@Transactional
public void save() {
  try{
     ...
  } catch (Exception e) {
    log.error("处理异常...");
    throw e;//应throw出去
  }
}

源码详解

事务处理流程图

3.1 拦截器入口

事务拦截器,使用注解或XMl方式开启的事务会被此类拦截。
org.springframework.transaction.interceptor�.TransactionInterceptor�是事务的核心处理逻辑
TransactionInterceptor主要方法:
image.png
TransactionInterceptor依赖关系
image.png

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {

	
	//构造函数,可以自己指定事务管理器,可以指定Properties以及TransactionAttributeSource
	public TransactionInterceptor() {
	}
	public TransactionInterceptor(PlatformTransactionManager ptm, Properties attributes) {
		setTransactionManager(ptm);
		setTransactionAttributes(attributes);
	}
	public TransactionInterceptor(PlatformTransactionManager ptm, TransactionAttributeSource tas) {
		setTransactionManager(ptm);
		setTransactionAttributeSource(tas);
	}

    //拦截器的入口
	@Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// 获取目标类
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// 调用TransactionAspectSupport(父类)的invokeWithinTransaction方法处理
        // invocation::proceed:继续执行AOP拦截器链路的下一个拦截器
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}

}

3.2 核心处理方法

真正的事务处理逻辑还是在父类-TransactionAspectSupport
�而父类的核心方法就是invokeWithinTransaction
类结构:
image.png

//处理事务的核心方法,protected修饰符,不允许其它包调用
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
	final InvocationCallback invocation) throws Throwable {

	// 获取事务属性源
	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);

    //如果txAttr为null,或者tm属于非CallbackPreferringPlatformTransactionManager(编程式事务),执行对应逻辑
    //在
	if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
		// 判断是否需要创建事务,根据事务传播机制做出对应处理。
		TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

		Object retVal;
		try {
			//回调方法执行,执行目标方法,处理原有业务逻辑
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			// 回滚事务
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
		}
		finally {
            //清楚事务标记
			cleanupTransactionInfo(txInfo);
		}
        //提交事务
		commitTransactionAfterReturning(txInfo);
		return retVal;
	}
        
	else {
		final ThrowableHolder throwableHolder = new ThrowableHolder();

		//以编程式的开启的事务走这里
		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;
		}
	}
}

3.2.1 以何种方式执行事务

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
	@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

	// 如果txAttr的name(事务名称)为空,就将方法标识作为事务名称
	if (txAttr != null && txAttr.getName() == null) {
		txAttr = new DelegatingTransactionAttribute(txAttr) {
			@Override
			public String getName() {
				return joinpointIdentification;
			}
		};
	}

    //获取事务
	TransactionStatus status = null;
	if (txAttr != null) {
		if (tm != null) {
			status = tm.getTransaction(txAttr);
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
						"] because no transaction manager has been configured");
			}
		}
	}
	return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

我们继续往下看,看底层是如何判断事务处理方式的(事务传播机制在这里体现)。

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
	Object transaction = doGetTransaction();
	boolean debugEnabled = logger.isDebugEnabled();

	if (definition == null) {
		// 如果没有事务定义,则使用默认值
		definition = new DefaultTransactionDefinition();
	}

	if (isExistingTransaction(transaction)) {
		// 如果当前有事务
		return handleExistingTransaction(definition, transaction, debugEnabled);
	}

	// 是否超时
	if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
		throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
	}

	// 如果存在事务以事务执行,如果不存在事务则抛出异常
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
		throw new IllegalTransactionStateException(
				"No existing transaction found for transaction marked with propagation 'mandatory'");
	}
    // 需要创建新事务执行的传播级别
	else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
			definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
			definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
		SuspendedResourcesHolder suspendedResources = suspend(null);
		if (debugEnabled) {
			logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
		}
		try {
			boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
			DefaultTransactionStatus status = newTransactionStatus(
					definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
			doBegin(transaction, definition);
			prepareSynchronization(status, definition);
			return status;
		}
		catch (RuntimeException | Error ex) {
			resume(null, suspendedResources);
			throw ex;
		}
	}
	else {
		// Create "empty" transaction: no actual transaction, but potentially synchronization.
		if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
			logger.warn("Custom isolation level specified but no actual transaction initiated; " +
					"isolation level will effectively be ignored: " + definition);
		}
		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
		return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
	
private TransactionStatus handleExistingTransaction(
		TransactionDefinition definition, Object transaction, boolean debugEnabled)
		throws TransactionException {
    //如果当前有事务,且隔离级别是PROPAGATION_NEVER,则抛出异常,对应传播机制序号5
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
		throw new IllegalTransactionStateException(
				"Existing transaction found for transaction marked with propagation 'never'");
	}
    //如果当前存在事务就加入事务方式执行
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
		if (debugEnabled) {
			logger.debug("Suspending current transaction");
		}
		Object suspendedResources = suspend(transaction);
		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
		return prepareTransactionStatus(
				definition, null, false, newSynchronization, debugEnabled, suspendedResources);
	}
    //以创建新事务方式执行
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
		if (debugEnabled) {
			logger.debug("Suspending current transaction, creating new transaction with name [" +
					definition.getName() + "]");
		}
		SuspendedResourcesHolder suspendedResources = suspend(transaction);
		try {
			boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
			DefaultTransactionStatus status = newTransactionStatus(
					definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
			doBegin(transaction, definition);
			prepareSynchronization(status, definition);
			return status;
		}
		catch (RuntimeException | Error beginEx) {
			resumeAfterBeginException(transaction, suspendedResources, beginEx);
			throw beginEx;
		}
	}
    //以嵌套事务方式执行
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
		if (!isNestedTransactionAllowed()) {
			throw new NestedTransactionNotSupportedException(
					"Transaction manager does not allow nested transactions by default - " +
					"specify 'nestedTransactionAllowed' property with value 'true'");
		}
		if (debugEnabled) {
			logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
		}
		if (useSavepointForNestedTransaction()) {
			// Create savepoint within existing Spring-managed transaction,
			// through the SavepointManager API implemented by TransactionStatus.
			// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
			DefaultTransactionStatus status =
					prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
			status.createAndHoldSavepoint();
			return status;
		}
		else {
			// Nested transaction through nested begin and commit/rollback calls.
			// Usually only for JTA: Spring synchronization might get activated here
			// in case of a pre-existing JTA transaction.
			boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
			DefaultTransactionStatus status = newTransactionStatus(
					definition, transaction, true, newSynchronization, debugEnabled, null);
			doBegin(transaction, definition);
			prepareSynchronization(status, definition);
			return status;
		}
	}
	// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
	if (debugEnabled) {
		logger.debug("Participating in existing transaction");
	}
	if (isValidateExistingTransaction()) {
		if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
			Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
			if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
				Constants isoConstants = DefaultTransactionDefinition.constants;
				throw new IllegalTransactionStateException("Participating transaction with definition [" +
						definition + "] specifies isolation level which is incompatible with existing transaction: " +
						(currentIsolationLevel != null ?
								isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
								"(unknown)"));
			}
		}
		if (!definition.isReadOnly()) {
			if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
				throw new IllegalTransactionStateException("Participating transaction with definition [" +
						definition + "] is not marked as read-only but existing transaction is");
			}
		}
	}
	boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    //转换为事务状态类
	return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

3.3 回滚事务

事务的前置逻辑说完了,我们来看下事务发生异常的处理逻辑

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
	//判断事务属性和状态不为空的情况
	if (txInfo != null && txInfo.getTransactionStatus() != null) {
		if (logger.isTraceEnabled()) {
			logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
					"] after exception: " + ex);
		}
        //判断是否需要回滚
		if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
			try {
				txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
			}
			catch (TransactionSystemException ex2) {
				logger.error("Application exception overridden by rollback exception", ex);
				ex2.initApplicationException(ex);
				throw ex2;
			}
			catch (RuntimeException | Error ex2) {
				logger.error("Application exception overridden by rollback exception", ex);
				throw ex2;
			}
		}
		else {
            //没有事务属性的情况为编程式事务,编程式事务不在此处回滚
			try {
				txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
			}
			catch (TransactionSystemException ex2) {
				logger.error("Application exception overridden by commit exception", ex);
				ex2.initApplicationException(ex);
				throw ex2;
			}
			catch (RuntimeException | Error ex2) {
				logger.error("Application exception overridden by commit exception", ex);
				throw ex2;
			}
		}
	}
}

3.3.1 是否需要回滚

回滚前会判断当前异常是否需要回滚

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;
    	// 如果自定义了需要处理的异常类型,那么就根据类的全限定名判断当前属性是否是需要回滚的异常类型
		if (this.rollbackRules != null) {
			for (RollbackRuleAttribute rule : this.rollbackRules) {
				int depth = rule.getDepth(ex);
				if (depth >= 0 && depth < deepest) {
					deepest = depth;
					winner = rule;
				}
			}
		}

		if (logger.isTraceEnabled()) {
			logger.trace("Winning rollback rule is: " + winner);
		}

		// 如果没有自定义异常类型,那么调用抽象类的rollbackOn方法判断是否需要回滚
		if (winner == null) {
			logger.trace("No relevant rollback rule found: applying default rules");
			return super.rollbackOn(ex);
		}
    	// 判断当时异常是否为不需要回滚的异常类型
		return !(winner instanceof NoRollbackRuleAttribute);
	}

那么我们继续看下父类的rollbackOn方法(重点)

@Override
public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}

我们可以看到这里指定了两个类型:RuntimeException和Error,可以得出结论事务默认之后捕捉运行时异常和Error,对于Exception不会进行回滚处理。


参考资料:
极客时间-Spring事务常见错误
Spring AOP MethodInvocation拦截器调用原理_bugpool的博客-CSDN博客
【小家Spring】源码分析Spring的事务拦截器:TransactionInterceptor和事务管理器:PlatformTransactionManager_YourBatman的博客-CSDN博客

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值