0
点赞
收藏
分享

微信扫一扫

自己动手写一个分库分表中间件(九)兼容性处理之事务之 Spring 怎么看是一个事务


兼容是本次分库分表项目中非常重要的一点,本文探讨的议题是兼容中的事务问题。

实在是不知道这个标题名称该怎么取。直接看一个例子吧,这也是是很多朋友经常有疑问的一个点。

//srvService1Mapper和srvService2Mapper分别配置了不同的数据源、事务管理器,srvService1Mapper的事务管理器标注了@Primary
@Transactional
public void bbb(String sId1,String sId2) {
srvService1Mapper.updateByid(sId1);
srvService2Mapper.updateByid(sId2);
int i = 1/0;
}

这个方法非常简单,一个被 ​​@Transactional​​​ 标注的方法,内部调用了两个配置了不同数据源和事务管理器的 ​​Mapper​​​ ,要注意的是,这里的 ​​@Transactional​​ 并未设置任何参数,即属性参数都用默认的。

那么这个方法在执行后,是 ​​srvService1Mapper​​​ 会回滚还是 ​​srvService2Mapper​​ 会回滚呢?

@Transactional 会使用哪个事务管理器?

先看第一个问题:​​@Transactional​​​ 会使用哪个事务管理器?很明显,如果指定了事务管理器的 ​​BeanName​​,那么肯定就是对应的事务管理器。如果没有指定的话就会使用默认事务管理器,那么默认事务管理器是什么呢,可以看下这个方法:

​org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction​​​->​​org.springframework.transaction.interceptor.TransactionAspectSupport#determineTransactionManager​​:

protected PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr) {
// Do not attempt to lookup tx manager if no tx attributes are set
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
return determineQualifiedTransactionManager(qualifier);
}
else if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.transactionManagerBeanName);
}
else {
PlatformTransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
if (defaultTransactionManager == null) {
defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
}
return defaultTransactionManager;
}
}

这个方法说白了就是先解析 ​​@Transactional​​​ 的属性,解析不出来就走默认的,这里的默认其实就是从 Bean 容器中找到就是被 ​​@Primary​​​ 标注的事务管理器,这个就看我们是怎么配置的,如果没有配置被 ​​@Primary​​ 标注的事务管理器,那么就会报错:

No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available: expected single matching bean but found 7:

上面也提到了,​​srvService1Mapper​​​ 的事务管理器标注了 ​​@Primary​​。

这个问题确定了,那么再看第二个问题。

怎么看是同一个事务

我们都知道,Spring 的声明式事务是通过 ​​@Transactional​​​ 来实现的,现在已经确定了,这里的 ​​@Transactional​​​ 使用的是 ​​srvService1Mapper​​ 的事务管理器。

这里“同一个事务”的指的是, ​​@Transactional​​​ 的事务管理器和被 ​​@Transactional​​​ 标注方法内部的 ​​Mapper​​​ 使用的事务是否是同一个。在这个例子中,很明显 ​​srvService1Mapper​​​ 和 ​​@Transactional​​ 是同一个。

事务的本质是什么

在“​​《从单机事务到分布式事务》分享文档​​​”中提到过,事务是 ​​Connection​​​ 级别的,​​Connection​​​ 从哪里来?从 ​​DataSource​​​ 里面来。这里有个要注意的是,​​Mapper​​​ 的 ​​SqlSessionFactory​​​ 要配置 ​​DataSource​​​,同时事务管理器也是要配置 ​​DataSource​​的:

​org.mybatis.spring.SqlSessionFactoryBean#setDataSource​​:

public void setDataSource(DataSource dataSource) {
if (dataSource instanceof TransactionAwareDataSourceProxy) {
// If we got a TransactionAwareDataSourceProxy, we need to perform
// transactions for its underlying target DataSource, else data
// access code won't see properly exposed transactions (i.e.
// transactions for the target DataSource).
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
} else {
this.dataSource = dataSource;
}
}

​org.springframework.jdbc.datasource.DataSourceTransactionManager#DataSourceTransactionManager(javax.sql.DataSource)​​:

public DataSourceTransactionManager(DataSource dataSource) {
this();
setDataSource(dataSource);
afterPropertiesSet();
}

@Transactional 从哪里获取 Connection

​@Transactional​​​ 开启的时候一开始就会去获取 ​​Connection​​​,即从 ​​@Transactional​​​ 配置的事务管理器中配置的 ​​DataSource​​​ 中获取 ​​Connection​​。

srvService1Mapper 从哪里获取 Connection

很明显,从配置的 ​​SqlSessionFactory​​​ 中配置的 ​​DataSource​​​ 中获取 ​​Connection​​​。​​srvService2Mapper​​同理。

Mapper 的 Connection 怎么和 @Transactional 结合起来

这里有个注意点,就是 Spring 与不同的数据库访问框架,实现细节会略有不同,但总体流程其实差不多。

上面已经提到过,事务是 ​​Connection​​​ 级别,也就是说这个 ​​Mapper​​​ 的事务要想生效,必须和 ​​@Transactional​​​ 的 ​​Connection​​ 是同一个。

流程实在太复杂,直接看最关键的:

​org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource​​:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

可以看到是从 MyBatis 的 ​​Configuration​​​ 获取的 ​​DataSource​​​,再看 ​​org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection​​:

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

ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
....
}

可以看到是从 ​​TransactionSynchronizationManager​​​ 中获取 ​​Connection​​​,即 ​​ThreadLocal​​​,简单点说就是从一个内存 ​​Map​​​ 中获取 ​​Connection​​​,key 是 ​​DataSource​​。

但这里是不是就很通透了,即只要 ​​DataSource​​​ 是一致的,那么 ​​Connection​​​ 就是一致的(当然这块是可以重写的,具体可以参看​​自己动手写一个分库分表中间件(七)分布式事务问题解决思路<三>动态 Connection​​​),那么就可以被 ​​@Transactional​​ 所管理。

再回过头看文章最开头的例子,很明显,只有 ​​srvService1Mapper​​​ 的事务会回滚,因为 ​​srvService2Mapper​​​ 的事务已经不受 ​​@Transactional​​ 管理了。

这里可能还有朋友有疑问,不受 ​​@Transactional​​ 管理了,那么这个事务怎么提交的呢,其实事务只要没有设置非自动提交,那么就会自动提交了。

本文比较详细的分析了 ​​DataSource​​ 在事务管理中的重要性,而这一点也是设计分库分表中间件中兼容历史场景的重要原理之一。

References

  • ​​https://mp.weixin.qq.com/s?__biz=MzU1OTgyMDc3Mg==&mid=2247485239&idx=1&sn=e2e54f7ad284e4266309953a199789fd&chksm=fc103dbccb67b4aa91737765caacea66574975600c9636fda527561978f70993c83fc68d3018&token=405931768&lang=zh_CN#rd​​


举报

相关推荐

0 条评论