Mybatis source code - SqlSession access

Posted by MartinGr on Thu, 09 Dec 2021 13:09:29 +0100

preface

It is known that when using Mybatis, the configuration file Mybatis config. Will be read first XML is a character stream or byte stream, then SqlSessionFactory is built based on the character stream or byte stream of the configuration file through SqlSessionFactory builder, and then SqlSession is obtained through the openSession() method of SqlSessionFactory. The example code is as follows.

public static void main(String[] args) throws Exception {
    String resource = "mybatis-config.xml";
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
            .build(Resources.getResourceAsStream(resource));
    SqlSession sqlSession = sqlSessionFactory.openSession();
}

The SqlSessionFactory in the above example code is actually DefaultSqlSessionFactory. This article will learn the process of DefaultSqlSessionFactory obtaining SqlSession.

text

The openSession() method of DefaultSqlSessionFactory is as follows.

public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

Continue to look at the implementation of the openSessionFromDataSource() method, as shown below.

private SqlSession openSessionFromDataSource(ExecutorType execType, 
                                             TransactionIsolationLevel level, 
                                             boolean autoCommit) {
    Transaction tx = null;
    try {
        //The Environment contains transaction factories and data sources
        final Environment environment = configuration.getEnvironment();
        //Get transaction factory
        //If JDBC transaction is configured, get JDBC transaction factory; otherwise, get MANAGED transaction factory
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        //Create transaction
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        //Create actuator
        final Executor executor = configuration.newExecutor(tx, execType);
        //Build DefaultSqlSession and return
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

The openSessionFromDataSource() method will first obtain the Environment from the Configuration, which contains the transaction factory and data source. The Environment is configured in the Mybatis Configuration file and will be added to the Configuration when loading the Mybatis Configuration file. Two transaction factories can be configured in the Configuration file of Mybatis: JDBC transaction factory and MANAGED transaction factory. They can create JDBC transactions and MANAGED transactions respectively. The class diagram is as follows.

Get the getTransactionFactoryFromEnvironment() method of the transaction factory, as shown below.

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
        //If a transaction factory is configured, the configured transaction factory is used
        //Mybatis can configure JDBC and MANAGED transaction factories
        return new ManagedTransactionFactory();
    }
    //Use MANAGED transaction factory without configuration
    return environment.getTransactionFactory();
}

If JDBC transaction is used in Mybatis, the transaction management is performed by Java sql. Connection is completed. If MANAGED transactions are used, Mybatis will hand over the transaction management to the WEB container (Tomcat, JBoss, etc.). Taking the commit() and rollback() methods as examples, let's take a look at the implementation of these two methods by JdbcTransaction and ManagedTransaction, as shown below.

public class JdbcTransaction implements Transaction {

    ......

    @Override
    public void commit() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Committing JDBC Connection [" + connection + "]");
            }
            connection.commit();
        }
    }

    @Override
    public void rollback() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Rolling back JDBC Connection [" + connection + "]");
            }
            connection.rollback();
        }
    }

    ......

}

public class ManagedTransaction implements Transaction {

    ......

    @Override
    public void commit() throws SQLException {
        //Does nothing
    }

    @Override
    public void rollback() throws SQLException {
        //Does nothing
    }

    ......

}

Return to the openSessionFromDataSource() method. After the transaction is created, the Executor will be built. Take a look at the implementation of the newexecutor (transaction, executortype, executortype) method, as shown below.

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //Create an Executor of the corresponding type according to the enumerated value of ExecutorType
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        executor = new SimpleExecutor(this, transaction);
    }
    //If L2 cache is enabled in Mybatis configuration file
    if (cacheEnabled) {
        //Create a cacheingexecution as the decorator of the Executor and add the L2 cache function to the Executor
        executor = new CachingExecutor(executor);
    }
    //Add plug-in logic to the Executor
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

In the above method of building the Executor executor, three main things are done, which are explained as follows.

The first thing is to create an Executor of the corresponding type according to the enumeration value of ExecutorType. The enumeration value of ExecutorType is as follows.

public enum ExecutorType {
    SIMPLE, REUSE, BATCH
}

Mybatis provides three types of executors: BatchExecutor, reuseexecution and simpleexecution. The class diagram is shown below.

Four abstract methods are defined in BaseExecutor, as shown below.

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
    throws SQLException;

protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
    throws SQLException;

These four abstract methods will be called in the methods provided by BaseExecutor. At the same time, these four abstract methods need to be implemented by BatchExecutor, reuseexecution and simpleexecution according to their respective functions. Therefore, the template design pattern is used in the design of Executor.

Second, if the L2 cache is enabled in the Mybatis configuration file, create a decorator cacheingexecution for the Executor to increase support for the L2 cache function. The relationship between caching Executor and Executor is as follows.

Cacheingexecution implements the Executor interface, and cacheingexecution holds the reference of Executor, which is the application of decorator mode. The caching in Mybatis will be introduced in subsequent articles.

The third thing is to add the logic of the plug-in to the Executor if the plug-in is configured. The plug-ins in Mybatis will be introduced in subsequent articles.

Return to the openSessionFromDataSource() method. After the Executor is created, the constructor of DefaultSqlSession will be called to create a DefaultSqlSession and return it. In the constructor of DefaultSqlSession, only a simple assignment is made, as shown below.

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
}

At this point, SqlSession is created.

summary

SqlSession is obtained through the openSession() method of SqlSessionFactory. The creation process of SqlSession mainly starts with the creation of transaction manager, then the creation of Executor based on transaction manager, and finally the creation of SqlSession based on Executor. Generally, SqlSessionFactory is DefaultSqlSessionFactory, and the created SqlSession is DefaultSqlSession.

Topics: Mybatis