前言
我们知道Spring Boot和Spring帮我们创建了数据源dataSource连接,当我们运用Spring事务或者@Transactional注解时,Spring对数据源是如何选择的呢?以及为什么在一个事务中不支持多数据源,如何做到支持多数据源。下面我们先来分析第一个问题,Spring事务的数据源选择问题。
一、Spring数据源选择问题
无论是Spring Boot 还是 Spring,道理都是一样的,这里拿Spring Boot做源码解析。在我们引入spring boot的pom文件的时候,会导入spring-boot-autoconfigure Jar包。包中spring.factories的会自动注入DataSourceAutoConfiguration、DataSourceTransactionManagerAutoConfiguration、TransactionAutoConfiguration三个类。
DataSourceAutoConfiguration的配置类非常简单,就是如果没有DateSource Bean,会默认创建一个HikariDataSource。
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
}
}
DataSourceTransactionManagerAutoConfiguration类
@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {
@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration {
private final DataSource dataSource;
private final TransactionManagerCustomizers transactionManagerCustomizers;
DataSourceTransactionManagerConfiguration(DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
this.dataSource = dataSource;
this.transactionManagerCustomizers = transactionManagerCustomizers.getIfAvailable();
}
@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public DataSourceTransactionManager transactionManager(DataSourceProperties properties) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(this.dataSource);
if (this.transactionManagerCustomizers != null) {
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}
}
}
DataSourceTransactionManagerAutoConfiguration类的作用是,如果我们没有自定义PlatformTransactionManager事务管理器,则会创建一个DataSourceTransactionManager默认事务管理器,注意此时已经将数据源set进DataSourceTransactionManager中了。
TransactionAutoConfiguration类比较简单,就是注入TransactionTemplate和TransactionInterceptor等。TransactionAutoConfiguration类中已经开启了@EnableTransactionManagement注解,我们在启动主类中可以不加。
我们知道在运行service需要开启事务时,是通过AbstractPlatformTransactionManager.getTransaction方法。
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction();
......................
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
..................
}
首先进入doGetTransaction方法,执行的是DataSourceTransactionManager.doGetTransaction
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
可以看到尝试获取TransactionSynchronizationManager.getResource(obtainDataSource())的ConnectionHolder。TransactionSynchronizationManager是一个用来保存当前线程的事务资源管理类。obtainDataSource()就是之前set的数据源。首次进入为空。
之后会执行DataSourceTransactionManager.doBegin方法。
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
.............
}
可以看到,如果ConnectionHolder为空,则会通过数据源新创建一个,之后绑定这个数据源的ConnectionHolder。也就是默认的情况下,开启一个事务只会有一个默认的数据源。并且绑定了这个数据源名称和ConnectionHolder。
后续当我们进入到Service中的具体Mapper方法时,
在mapper执行语句前,会被SqlSessionInterceptor拦截创建sqlsession
getSqlSession方法中,会从当前sessionFactory中获取一个sqlsession,第一次是没有的,创建后会绑定。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
之后会进入Object result = method.invoke(sqlSession, args);方法。该方法主要进入SimpleExecutor类执行。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
在获取Connection中,调用SpringManagedTransaction.getConnection方法
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
SpringManagedTransaction类中
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
}
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
}
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(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
//如果没有事务,或没有获取到,则从数据源中获取一个连接
Connection con = dataSource.getConnection();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
}
如果包含事务(一般mapper都是在事务中),实际的连接会先从之前事务创建的ConnectionHolder中获取。所以这里Mapper执行的Sql就是之前Spring事务中已经创建好的数据源。如果我们没有使用@Transactional注解,那么之前就不会被TransactionInterceptor拦截,DataSourceTransactionManager也不会执行doBegin,自然也就没有数据源连接。
如果没有事务,或没有获取到,则执行Connection con = dataSource.getConnection()从数据源中新获取一个连接。
引发的问题思考
现在我们的SpringBoot一般都是多数据源配置,比如配置多个DataSource,多个事务管理器。
比如这样
@Primary
@Bean(name = "test1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.test1")
@Qualifier("dataSourceList")
public DataSource getDateSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "test2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.test2")
@Qualifier("dataSourceList")
public DataSource getDateSource2() {
return DataSourceBuilder.create().build();
}
@Bean(name = "SqlSessionFactory")
public SqlSessionFactory oneSqlSessionFactory(@Qualifier("test1DataSource") DataSource test1DataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(test1DataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
return bean.getObject();
}
我们再看一下MybatisAutoConfiguration的装配类。
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
........................
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
return factory.getObject();
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
.....................
}
也就是如果我们没写SqlSessionFactory,则会默认创建SqlSessionFactory,数据源为带@Primay的。如果我们自己创建,也需要为SqlSessionFactory指定数据源,之后SqlSessionTemplate会将SqlSessionFactory作为入参创建。
所以假如我们执行一个带@Transactional的Service,创建的事务数据源会是@Primay的DataSource,如果此时创建Mapper的dataSource和这个Service事务中的数据源是同一个,那么会引用事务中的数据源,并会在Service结束提交。如果Service中执行第二个Mapper的数据源和第一个Mapper不是同一个,因为一个事务控制只会注入一个dataSrouce,那么第二个Mapper的SqlSessionTemplate会是另一个,SqlSessionTemplate中注入的数据源也是另一个,在获取dataSource时,因为当前TransactionSynchronizationManager.getResource(obtainDataSource())的数据源还是第一个Mapper的,所以会新创建SqlSession,并在Mapper的方法执行后提交事务,虽然两个Mapper在一个Service中,但第二个Mapper不受Service事务控制。
所以如果多数据源进行查询操作时,不要让这个Service方法成为@Transactional即可。或者写到Controller中。
下一节说如何做到数据源插入或者查询。