引文
上一篇文章我们讲述了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
类结构如下
这里我们重点来关注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 事务管理器
听名字就知道它是管理事务的操作的,它只包含三个方法。获取事务,回顾事务,提交事务。
TransactionDefinition 定义事务的类型
事务包含很多属性,是否可读,事务隔离级别,事务传播级别。通过事务的定义,我们根据定义获取特定的事务。
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就行。原理图如下所示:
如果是单数据源的情况,事务肯定是一致的。多数据源的情况下需要另外考虑。