互联网整合营销推广/郴州seo网络优化
欢迎关注微信公众号:冬瓜白
相关文章:
- 自己动手写一个分库分表中间件(一)思考
- 自己动手写一个分库分表中间件(二)数据源定义和分片代理层设计
- 自己动手写一个分库分表中间件(三)数据源路由实现
- 自己动手写一个分库分表中间件(四)表路由、SQL 重写和结果集处理思路
- 自己动手写一个分库分表中间件(五)分布式事务问题解决思路<一>基于 Spring 编程式事务
- 自己动手写一个分库分表中间件(六)分布式事务问题解决思路<二>动态事务管理器
- 自己动手写一个分库分表中间件(七)分布式事务问题解决思路<三>动态 Connection
- 自己动手写一个分库分表中间件(八)测试过程中的特殊 BUG<一>加解密拦截器的兼容问题
兼容是本次分库分表项目中非常重要的一点,本文探讨的议题是兼容中的事务问题。
实在是不知道这个标题名称该怎么取。直接看一个例子吧,这也是是很多朋友经常有疑问的一个点。
//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 setif (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