【源码】SpringBoot编程式事务使用及执行原理

Spring事务

1、【源码】SpringBoot事务注册原理

2、【源码】Spring Data JPA原理解析之事务注册原理

3、【源码】Spring Data JPA原理解析之事务执行原理

4、【源码】SpringBoot编程式事务使用及执行原理

5、【源码】Spring事务之传播特性的详解

6、【源码】Spring事务之事务失效及原理

前言

《Spring事务系列》的前面几篇博文讲解了通过@Transactional注解实现事务的原理,在Spring中,还有另外一种方式可以实现事务,那就是接下去要分享的编程式事务。在开始讲解编程式事务之前,先来看一个编程式事务的例子。

编程式事务示例

在使用编程式事务时,同样需要在项目中添加@EnableTransactionManagement注解,对于SpringBoot项目,默认已经添加了@EnableTransactionManagement注解。示例如下:

java 复制代码
@Service
public class MemberStatisticsService {

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private MemberStatisticsRepository memberStatisticsRepository;

    public int addStatistics(MemberStatisticsEntity entity) {
        // 省略其他
        // 开启编程式事务
        boolean rs = transactionTemplate.execute((status) -> {
            // 省略其他
            memberStatisticsRepository.save(entity);
            return true;
        });
        return rs ? 1 : 0;
    }

}

使用编程式事务只需两步:

1)引入TransactionTemplate;

2)在需要使用事务的方法中,只需transactionTemplete.execute()方法,在方法内部的回调方法中,添加需要事务保护的数据库相关操作的业务代码即可;

针对以上的示例,可能有人会想,把其中需要事务保护的数据库操作的业务代码剥离为独立的方法,在方法中添加@Transactional注解,在原方法中调用不就可以了嘛。示例如下:

java 复制代码
@Service
public class MemberStatisticsService {

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private MemberStatisticsRepository memberStatisticsRepository;

    public int addStatistics(MemberStatisticsEntity entity) {
        // 省略其他
        // 开启事务
        boolean rs = addStatisticsBs(entity);
        return rs ? 1 : 0;
    }

	@Transactional
    public int addStatisticsBs(MemberStatisticsEntity entity) {
		// 省略其他
		memberStatisticsRepository.save(entity);
        return true;
	}

}

如果这样写,当addStatisticsBs()方法报错的时候,会发现事务并没有回滚,这到底是为何呢?要回答这个问题,还得从源码说起。

事务失效原因

【源码】SpringBoot事务注册原理-CSDN博客

在上面的博客中介绍了方法中添加@Transactional注解时,该类会生成代理类,代理类中添加了TransactionInterceptor拦截器,从而实现了事务管理。

当addStatistics()方法执行时,会先执行ReflectiveMethodInvocation.proceed()方法,循环遍历所有的拦截器。执行完所有拦截器之后,再执行动态代理对象的target类的对应方法,即原方法。详见:

【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码-CSDN博客

博客中的动态代理方法拦截部分。

因为addStatistics()没有添加@Transactional注解,所以执行target的addStatistics()方法,所以在addStatistics()方法内部的this对象是target,而不是代理对象。所以在addStatistics()内部调用addStatisticsBs()方法时,是执行target的addStatisticsBs()方法,所以不再先执行ReflectiveMethodInvocation.proceed(),也就不会执行TransactionInterceptor拦截器,所以没有开启事务管理。

编程式事务的优缺点

3.1 优点

1)灵活性强:开发人员可以在代码中根据具体业务需要来控制事务的范围,特别是大事务或高并发场景,可以缩小事务范围;

2)易于调试:由于事务管理在代码层实现,可以容易的追踪事务管理的细节;

3.2 缺点

1)代码复杂度高:需要手动处理事务,可能增加工作量和代码的复杂度;

2)实现复杂度较高:事务的范围和属性信息需要在代码中显示声明,可能导致一些特定的业务难以满足;

编程事务实现原理

3.1 TransactionTemplate的注入

SpringBoot启动的时候,在TransactionAutoConfiguration配置类中,自动注入TransactionTemplate。代码如下:

java 复制代码
public class TransactionAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnSingleCandidate(PlatformTransactionManager.class)
	public static class TransactionTemplateConfiguration {

		@Bean
		@ConditionalOnMissingBean(TransactionOperations.class)
		public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
			return new TransactionTemplate(transactionManager);
		}

	}
	
}

在SpringBoot框架中,会自动引入TransactionAutoConfiguration。且在META-INF的spring-autoconfigure-metadata.properties有如下配置:

XML 复制代码
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration=
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$TransactionTemplateConfiguration=
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$TransactionTemplateConfiguration.ConditionalOnSingleCandidate=org.springframework.transaction.PlatformTransactionManager

说明在自动引入TransactionAutoConfiguration时,先引入内部类TransactionAutoConfiguration。

3.2 TransactionTemplate

TransactionTemplate的代码如下:

java 复制代码
package org.springframework.transaction.support;

@SuppressWarnings("serial")
public class TransactionTemplate extends DefaultTransactionDefinition
		implements TransactionOperations, InitializingBean {

	/** Logger available to subclasses */
	protected final Log logger = LogFactory.getLog(getClass());

	@Nullable
	private PlatformTransactionManager transactionManager;

	public TransactionTemplate() {
	}

	public TransactionTemplate(PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

	public TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) {
		super(transactionDefinition);
		this.transactionManager = transactionManager;
	}

	public void setTransactionManager(@Nullable PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

	@Nullable
	public PlatformTransactionManager getTransactionManager() {
		return this.transactionManager;
	}

	@Override
	public void afterPropertiesSet() {
		if (this.transactionManager == null) {
			throw new IllegalArgumentException("Property 'transactionManager' is required");
		}
	}

	/**
	 * 执行事务
	 */
	@Override
	@Nullable
	public <T> T execute(TransactionCallback<T> action) throws TransactionException {
		Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

		if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
			return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
		}
		else {
			// 获取一个TransactionStatus对象,此实现处理传播行为
			TransactionStatus status = this.transactionManager.getTransaction(this);
			T result;
			try {
				// 执行入参action的方法,即业务处理方法
				result = action.doInTransaction(status);
			}
			catch (RuntimeException | Error ex) { // 处理RuntimeException和Error的异常,执行回滚,抛对应异常
				// Transactional code threw application exception -> rollback
				rollbackOnException(status, ex);
				throw ex;
			}
			catch (Throwable ex) { // 处理Throwable异常,也会执行回滚,抛UndeclaredThrowableException异常
				// Transactional code threw unexpected exception -> rollback
				rollbackOnException(status, ex);
				throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
			}
			this.transactionManager.commit(status);
			return result;
		}
	}
	
	/**
	 * 执行异常回滚
	 */
	private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
		Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

		logger.debug("Initiating transaction rollback on application exception", ex);
		try {
			// 执行回滚
			this.transactionManager.rollback(status);
		}
		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;
		}
	}


	@Override
	public boolean equals(Object other) {
		return (this == other || (super.equals(other) && (!(other instanceof TransactionTemplate) ||
				getTransactionManager() == ((TransactionTemplate) other).getTransactionManager())));
	}

}

3.2.1 execute()的核心功能如下:

1)执行TransactionManager.getTransaction(),实际执行AbstractPlatformTransactionManager.getTransaction()方法。根据事务的传播行为,开启事务,获取TransactionStatus对象,详见

【源码】Spring Data JPA原理解析之事务执行原理-CSDN博客

2)调用action.doInTransaction(status),执行入参action的方法,即业务处理方法。业务处理方法传入的参数为TransactionStatus对象;

3)如果业务方法抛RuntimeException或Error类型的异常,则执行回滚,抛对应异常;

4)如果业务方法抛Throwable异常,也执行回滚,抛UndeclaredThrowableException异常;

这是和通过@Transactional注解实现事务时处理的差异,在@Transactional注解实现的事务异常时,默认只处理RuntimeException或Error类型的异常,或者是注解中声明的回滚信息。

5)如果业务方法没有抛异常,则执行TransactionManager.commit()方法;

3.2.2 rollbackOnException()方法中直接调用TransactionManager.rollback(),执行回滚。

业务异常捕获及回滚

通过前面的分析,不管是通过@Transactional注解实现事务还是编程式事务,都是在业务逻辑出现异常时,事务处理会捕获异常,并判断是否要进行事务回滚,然后抛出对应异常。在某些场景中,需要在业务中自己捕获异常,此时就需要使用别的方式实现事务的回滚。

因为业务中自己捕获了异常,所以在事务管理中,会执行事务的提交。事务提交的源码如下:

java 复制代码
package org.springframework.transaction.support;

@SuppressWarnings("serial")
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

	@Override
	public final void commit(TransactionStatus status) throws TransactionException {
		if (status.isCompleted()) {
			throw new IllegalTransactionStateException(
					"Transaction is already completed - do not call commit or rollback more than once per transaction");
		}

		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
		// 如果本地代码设置了回滚
		if (defStatus.isLocalRollbackOnly()) {
			if (defStatus.isDebug()) {
				logger.debug("Transactional code has requested rollback");
			}
			processRollback(defStatus, false);
			return;
		}

		// 全局事务被标记为仅回滚,但事务代码请求提交
		if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
			if (defStatus.isDebug()) {
				logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
			}
			processRollback(defStatus, true);
			return;
		}
		// 提交处理
		processCommit(defStatus);
	}

}

在commit()方法中,在真正提交处理前,会先进行两个判断:

1)defStatus.isLocalRollbackOnly()如果返回true,会执行回滚;

isLocalRollbackOnly()默认返回false,可以通过setRolbackOnly()修改为true。

在TransactionAspectSupport类中,有一个currentTransactionStatus()的静态方法,通过该方法就可以获取到TransactionStatus对象,调用setRollbackOnly()即可。

注:该方法也适用于使用@Transactional注解的方法

2)判断!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly(),如果为true,也会执行回滚;

结尾

本篇就先分享到这里。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

相关推荐
JingAi_jia9174 个月前
【源码】Spring Data JPA原理解析之Auditing执行原理
spring源码·springboot源码·jpa源码·auditing源码·jpa审计原理·jpa审计执行原理
Jacky(易小天)5 个月前
多角度剖析事务和事件的区别
数据库·event·transaction
tmax52HZ5 个月前
SpringBoot源码(自动装配、内嵌Tomcat)
springboot·springboot自动装配·springboot源码·内嵌tomcat·tomcat源码·钩子函数·springboot依赖管理
没拖拉机的斯基8 个月前
Spring事务管理
数据库·spring boot·spring·spring事务
放羊的牧码9 个月前
SpringBoot - 不加 @EnableCaching 标签也一样可以在 Redis 中存储缓存?
spring boot·redis·缓存·注解·transaction·enablecaching·enableasync
丁总学Java1 年前
mybatis数据输入-实体类型的参数
mybatis·commit·manager·sqlsession·transaction·insert·resources
小明java问道之路1 年前
【Spring】Spring事务失效问题
java·后端·spring·事务·异常·spring事务·隔离机制
smart哥1 年前
JDK并发修改异常的一个“BUG“
java·spring源码
泠青沼~1 年前
【Spring】条件注解
java·后端·spring·gradle·spring源码