One of the issues to be discussed in this article is how to set up different database data sources for Spring and Mybatis?
be careful. Under normal circumstances, you must set the same database data source for Spring and Mybatis
Case code location https://github.com/infuq/spring-framework/tree/main/infuq-t/src/main/java/com/infuq/mybatis
Case code structure
//AppConfig.java import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; @EnableTransactionManagement @MapperScan("com.infuq.mybatis.mapper") @ComponentScan public class AppConfig { // data source @Bean public DataSource druidDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/test_0?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true"); dataSource.setUsername("root"); dataSource.setPassword("9527"); return dataSource; } // data source @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/test_1?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true"); dataSource.setUsername("root"); dataSource.setPassword("9527"); return dataSource; } // Mybatis needs a SqlSessionFactory, so inject a SqlSessionFactory into the container @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject(); } // Transaction manager for transaction management @Bean public DataSourceTransactionManager druidTransactionManager(DataSource druidDataSource) { return new DataSourceTransactionManager(druidDataSource); } }
Graphically describe the above AppConfig Structure of Java code
The two database data sources are set to SqlSessionFactory and transaction manager respectively
SqlSessionFactory is used when Mybatis operates the database, such as insert,update, etc
The transaction manager is used to open transactions in Spring
// UserServiceImpl.java import com.infuq.mybatis.mapper.UserMapper; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.transaction.annotation.Transactional; public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Transactional(transactionManager = "druidTransactionManager") @Override public void getList() { userMapper.getList(); } }
The transaction manager is used in the code, but select is used as a case explanation instead of insert/update. Readers should not care too much
After the program runs, take a look at the UserServiceImpl instance and UserMapper instance that exist in the Spring container
The proxy object of Service is stored in the container. There is a real proxy object (i.e. a real UserServiceImpl instance) in the proxy object. Inside the proxy object, there is a mapper proxy object. The mapper proxy object holds the sqlSessionFactory object and sqlSessionFactory holds the data source
There is also a transaction interceptor TransactionInterceptor inside the proxy object of the Service
On the call link, a transaction interceptor will be called between the Service proxy object and the Service proxy object
Start running program
After running the program, first call the service proxy object, and then call the transaction interceptor. In this transaction interceptor, we get the transaction manager in the container, TransactionManager, which we configured earlier
//AppConfig.java @Bean public DataSourceTransactionManager druidTransactionManager(DataSource druidDataSource) { return new DataSourceTransactionManager(druidDataSource); }
The transaction manager has a very important thing to do It needs to get a database connection and start a transaction
So where does the database connection come from?
When configuring the transaction manager, set a data source for it, and the transaction manager will get a database connection from this data source And it is implemented through ThreadLocal If a thread uses multiple database data sources during execution, the relationship between a data source and a database connection will be saved in ThreadLocal to ensure that the thread will only use the same database connection when operating a database The specific implementation is at org springframework. jdbc. datasource. DataSourceTransactionManager#doBegin
@Override protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; Connection con = null; try { if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { // Get the data source set in the transaction manager and create a database connection based on the data source Connection newCon = obtainDataSource().getConnection(); txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } txObject.getConnectionHolder().setSynchronizedWithTransaction(true); con = txObject.getConnectionHolder().getConnection(); Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); txObject.setReadOnly(definition.isReadOnly()); if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); // Open transaction con.setAutoCommit(false); } prepareTransactionalConnection(con, definition); txObject.getConnectionHolder().setTransactionActive(true); int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } if (txObject.isNewConnectionHolder()) { // Save the datasource - > connection relationship to ThreadLocal TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } } }
Summary Spring will put the proxy object of the Service into the container. When calling the method of the proxy object, it will first call the transaction interceptor, which will get the transaction manager in the container. The transaction manager will create a database connection and start the transaction according to the set data source At the same time, the data source - > database connection will also be saved to ThreadLocal
Next, let's look at the code logic at the Mybatis level
After layers of calls, Mybatis also needs to get the database connection for the next operation of the database So how did you get this connection?
Mybatis originally intended to get a database connection from ThreadLocal, but the data source held by mybatis does not have a corresponding database connection in ThreadLocal, and the existing data sources in ThreadLocal are placed in the transaction manager. They are not the same data source
Therefore, Mybatis needs to create a database connection according to its own data source And put it in ThreadLocal
As shown in the figure above, at the beginning of the article, different data sources are set when configuring the transaction manager and SqlSessionFactory. Finally, when the transaction manager starts A transaction, it uses the data source A to create A database connection When Mybatis actually operates the database, it uses A database connection created by data source B As A result, the connection between opening transaction and actual database operation is not the same connection
official account