JPA与TkMybatis事物机制

8 篇文章 0 订阅
6 篇文章 0 订阅

引文

上一篇文章我们讲述了DDD+CQRS如何实现优雅查询(见:DDD+CQRS架构如何优雅实现查询),大家肯定会有很多疑问,比如Spring是如何保证JPA与TkMybatis之间的事务一致性的问题。这篇文章我来带领大家走进Spring JPA与TkMybatis的事务原理。
推荐大家一个专业做技术的公众号:
在这里插入图片描述

Spring JPA事务原理

在讲Spring JPA的事务原理之前我们先来讲一下Spring的事务原理。Spring的整个事务原理其实非常简单,但是如果将其打开来讲,其实也是非常复杂的,因为其支持的业务场景非常复杂。总所周知,Spring的事务机制是通过AOP来实现,大致流程图如下。
在这里插入图片描述
这里我们就不直接展开来讲Spring 事务了,原因刚才也说了,Spring事务如果完全展开来讲,也不是一篇博客能够讲清楚的。接下来我们对照这个流程图来讲清楚实现Spring Transcation的几个核心对象(对于核心对象我也就不仔细展开介绍了,具体大家可以看源码)。既然是AOP实现的,首先我们来看它的拦截类TransactionInterceptor。
TransactionInterceptor
类结构如下
TransactionInterceptor类结构
这里我们重点来关注invoke方法

    @Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		//获取目标对象
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
		// 调用TransactionAspectSupport类的invokeWithinTransaction方法实现事务控制
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}

我们发现invoke方法调用的invokeWithinTransaction方法来自TranscationAspectSupport类,接下来我们来重点介绍一下TranscationAspectSupport类。
TranscationAspectSupport
TranscationAspectSupport是真正实现事务控制的类,该类的内部有几个核心的对象,我们抓住这几个核心的对象也就理解了总体,剩下的可以花一点时间就可以完全理解了。
PlatformTransactionManager 事务管理器
听名字就知道它是管理事务的操作的,它只包含三个方法。获取事务,回顾事务,提交事务。
PlatformTransactionManager
TransactionDefinition 定义事务的类型
事务包含很多属性,是否可读,事务隔离级别,事务传播级别。通过事务的定义,我们根据定义获取特定的事务。
TransactionDefinition
TransactionStatus
TransactionStatus 代表一个事务运行的状态,事务管理器通过状态可以知道事务的状态信息,然后进行事务的控制。事务是否完成,是否是新的事务,是不是只能回滚等。
TransactionStatus
TransactionInfo
TransactionInfo是TranscationAspectSupport的一个内部类,该类包含了事务管理器,定义的事务属性,切入点标识,事务状态,上一个事务状态等属性
在这里插入图片描述
接下来我们来看看invokeWithinTransaction的主要流程

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

		//1. 首先获取我们定义的事务属性,可能是定义在XML中,也可能是定义在注解上
		TransactionAttributeSource tas = getTransactionAttributeSource();
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		//2.根据定义的事务属性获取PlatformTransactionManager,然后获取加入点的标识
		final TransactionManager tm = determineTransactionManager(txAttr);

		if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
			ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
				if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
					throw new TransactionUsageException(
							"Unsupported annotated transaction on suspending function detected: " + method +
							". Use TransactionalOperator.transactional extensions instead.");
				}
				ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
				if (adapter == null) {
					throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
							method.getReturnType());
				}
				return new ReactiveTransactionSupport(adapter);
			});
			return txSupport.invokeWithinTransaction(
					method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
		}

		PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
		//3.事务处理部分,判断是不是要新建事务
		if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
			//4.将事务等相关信息保存在txInfo对象中
			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				//5.执行目标代码,指我们的业务方法
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				//6.失败了执行回滚事务
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				//7.清理事务信息
				cleanupTransactionInfo(txInfo);
			}

			if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
				TransactionStatus status = txInfo.getTransactionStatus();
				if (status != null && txAttr != null) {
					retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
				}
			}
			//8.如果没有抛出异常提交事务
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
			....}
	}

大家可以对照我上述的注解来看。大体流程是这样。需要更多的了解可以自己对照Spring源码来看。
Spring JPA的PlatformTransactionManager实现类是JpaTranscationManager。
在这里插入图片描述
JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。不管框架如何封装,其实最终内部也是调用connection对象来实现操作。讲到这里大家应该明白了,JPA与TkMybatis能否保证是否一致性的关键在于connection对象是否是同一个。接下来我们来看TkMybatis事务控制原理。

TkMybatis事务控制原理

TkMybatis的事务管理主要是通过Transaction对象来实现的。
Transaction
Transaction一共有五个方法,看方法名应该都知道,分别是获取connection对象,提交,回滚,关闭,获取超时。
在这里插入图片描述
Transaction类一共有三个实现分别是
JdbcTransaction:内部依赖connection和datasource进行事务控制。connection和datasource的来源都是通过构造注入进来的,能否保住与jpa的connection对象一致,需要进一步研究。
ManagedTransaction:commit和rollback目前都是空实现。
SpringManagedTransaction:内部只有一个入参为DataSource的构造方法,通过DataSource对象我们可以获取connection对象。

通过debug我们发现当spring boot同时整合Tkmybatis与spring jpa时,Transaction的实现类为SpringManagedTransaction
在这里插入图片描述
接下来我们重点关注一下SpringManagedTransaction类的connection对象如何获取的。

private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug(
          "JDBC Connection ["
              + this.connection
              + "] will"
              + (this.isConnectionTransactional ? " " : " not ")
              + "be managed by Spring");
    }
  }

重点来看一下DataSourceUtils.getConnection(this.dataSource);

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
		Assert.notNull(dataSource, "No DataSource specified");

		ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
		if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
			conHolder.requested();
			if (!conHolder.hasConnection()) {
				logger.debug("Fetching resumed JDBC Connection from DataSource");
				conHolder.setConnection(fetchConnection(dataSource));
			}
			return conHolder.getConnection();
		}
		......}

看到这一句TransactionSynchronizationManager.getResource(dataSource)。这个对象我们阅读Spring tx源码时发现是用threadlocal来实现对ConnectionHolder的保存的。所以可以看出在同一个DataSource的情况下,两者的connection肯定是同一个。

总结

不管jpa还是mybatis,Spring只是基于公共事务管理器,定制了一个实现,加入了ORM层一些处理而已。而且,事务是基于AOP的,具体代码里面做啥,他不关注的,哪怕你用了jpa,又用了mybatis,又用了jdbc-template,无所谓的,只要不是你给他们每一个都指定了一个DataSource就行。原理图如下所示:
在这里插入图片描述
如果是单数据源的情况,事务肯定是一致的。多数据源的情况下需要另外考虑。

参考链接:mybatis事务和spring整合mybatis事务

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值