I was asked a question by my colleague before: In our project, the opening and closing of transactions are in the charge of Spring, but the specific SQL statements are executed by Mybatis. How can Mybatis ensure that the SQL statement it executes is in Spring’s transaction context?

The original address: www.jianshu.com/p/6a880d20a…

Note: This article is not focused on analyzing the implementation principles of Spring transactions, but it will make it easier to read by understanding some of the principles of Spring transactions in advance

At present, most of the mainstream development framework of the company uses Spring + Mybatis to operate the database, and all transaction operations are managed by Spring. When we need a database operation with a Transactional context, we simply write a method that operates on the database and annotate it with the @Transactional annotation.

Think about this process for a moment. @Transactional is handled by Spring. All Spring does is fetch a database connection from a data source (usually a database connection pool, such as Druid, C3P0, etc.). The setAutoCommit(false) operation is performed before the method logic is entered, and the commit or rollback operations are performed when the processing is successful or an exception occurs.

Mybatis (mybatis, Mybatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBatis Spring and Mybatis must use the same connection, what is the real situation? How do they work seamlessly together? Let’s analyze it through the source code.

First of all, if we want to use Mybatis in Spring, we need to introduce a package: Mybatis – Spring in addition to the myBatis dependency.

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>x.x.x</version>
</dependency>Copy the code

You can guess that this dependency package is the key to Spring’s seamless connection with Mybatis.

In general, our configuration files in projects tend to look like this:

<! -- Session factory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/> </bean> <! -- Spring transaction Management --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/> </bean> <! <tx:annotation-driven transaction-Manager ="transactionManager" />Copy the code

The sqlSessionFactory and Spring transactionManager dataSource must be the same. 2. The sqlSessionFactory type here is org. Mybatis. Spring. SqlSessionFactoryBean, this class is introduced by our package mybatis – provided by the spring.

The SqlSessionFactoryBean is a factory bean, which means that the actual instance it gives to Spring is provided by the getObject() method.

@Override
public SqlSessionFactory getObject() throws Exception {
    if(this.sqlsessionFactory == null) {afterPropertiesSet(); }returnthis.sqlSessionFactory; } @override public void afterPropertiesSet() throws Exception {// this.sqlSessionFactory = buildSqlSessionFactory(); BuildSqlSessionFactory () throws IOException {//... // Omit some other initialization information and focus on the transaction logicif(this.transactionFactory == null) { Put the transaction operations to SpringManagedTransactionFactory in mybatis to do this. TransactionFactory = new SpringManagedTransactionFactory (); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // omit the following logic //... }Copy the code

We could look at the below SpringManagedTransactionFactory class source code:

public class SpringManagedTransactionFactory implements TransactionFactory {

  /**
   * {@inheritDoc}
   */
  @Override
  public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    return new SpringManagedTransaction(dataSource);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Transaction newTransaction(Connection conn) {
    throw new UnsupportedOperationException("New Spring transactions require a DataSource");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setProperties(Properties props) {
    // not needed in this version
  }

}Copy the code

There is very little code, and only one method is valid. It seems that we are getting closer to success. Keep following to see the source code for SpringManagedTransaction:

@Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
  }Copy the code

Omit the rest of the class, we focus on getting connection, the key place here on this. The connection = DataSourceUtils. GetConnection (enclosing a dataSource); , DataSourceUtils name is org) springframework). The JDBC datasource. DataSourceUtils, yes, it is provided by the Spring class, according to our conjecture, Spring open after the transaction, The DataSourceUtils class is provided by Spring, so Mybatis must have the same database connection as Spring to allow its SQL statement to operate in the transaction context. Let’s look at the DataSourceUtils source code to verify:

// Getting a database Connection ultimately falls on this method, and I remove some unimportant code public static ConnectiondoThrows SQLException GetConnection (DataSource DataSource) {/ / TransactionSynchronizationManager key!!!!!! Does it feel familiar? ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);if(conHolder == null || ! conHolder.hasConnection() && ! conHolder.isSynchronizedWithTransaction()) { Connection con = fetchConnection(dataSource);if (TransactionSynchronizationManager.isSynchronizationActive()) {
                ConnectionHolder holderToUse = conHolder;
                if (conHolder == null) {
                    holderToUse = new ConnectionHolder(con);
                } else {
                    conHolder.setConnection(con);
                }

                holderToUse.requested();
                TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));
                holderToUse.setSynchronizedWithTransaction(true);
                if (holderToUse != conHolder) {
                    TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
                }
            }
            return con;
        } else {
            conHolder.requested();
            if(! conHolder.hasConnection()) { conHolder.setConnection(fetchConnection(dataSource)); }returnconHolder.getConnection(); }}Copy the code

See TransactionSynchronizationManager very cordial feeling? If you are familiar with Spring transaction management source code, you will immediately associate it with Spring transaction, which is to put the corresponding database connection here. I will intercept the source code and have a look:

if (txObject.isNewConnectionHolder()) {
    TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}Copy the code

This code is specific in our org. The configuration above springframework). The JDBC datasource. DataSourceTransactionManager the doBegin method in the class. As for TransactionSynchronizationManager class the implementation of the principle, in fact, I think you have guessed, that’s right, is a classic in the Java class library ref;!!!!!!

Spring + Mybatis transaction data source acquisition logic:

Spring-mybatis transaction process