欢迎关注微信公众号:冬瓜白
相关文章:
兼容是本次分库分表项目中非常重要的一点,本文探讨的议题是兼容中的事务问题。
实在是不知道这个标题名称该怎么取。直接看一个例子吧,这也是是很多朋友经常有疑问的一个点。
//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
在事务管理中的重要性,而这一点也是设计分库分表中间件中兼容历史场景的重要原理之一。