MyBatis source code interpretation of SqlSession

1. Purpose

Through the source code analysis of SqlSession function implementation, how to create and how to integrate in Spring.

2. SqlSession function introduction

The main Java interface for MyBatis work, through which you can execute commands, get mappers and manage transactions
-- code comments

View larger image

image

As you can see in the figure, the methods we use to operate the database are all in it.

3. SqlSession specific function implementation

image

From the class diagram, you can see that SqlSession has two implementation classes, DefaultSqlSession and SqlSessionManager.

  • DefaultSqlSession is the default implementation class of SqlSession, not thread safe
  • SqlSessionManager is a thread-safe SqlSession implementation, using ThreadLocal to save the created SqlSession

3.1 DefaultSqlSession source code analysis

/**
 *
 * The default implementation for {@link SqlSession}.
 * Note that this class is not Thread-Safe.
 * SqlSession 默认实现,非线程安全
 * 
 * @author Clinton Begin
 */
public class DefaultSqlSession implements SqlSession {


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

/**
   * 返回单个查询结果
   * @param statement 唯一标识匹配的语句.
   * @param parameter 查询参数.
   * @param <T>
   * @return
   */
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      //期待返回一条记录,但返回了多条
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }
  
  /**
   * 返回集合结果
   * @param statement 唯一标识匹配的语句
   * @param parameter 查询参数
   * @param rowBounds  返回结果的大小控制
   * @param <E>
   * @return
   */
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * 返回Map对象
   * @param statement 唯一标识匹配的语句.
   * @param parameter 查询参数
   * @param mapKey key值,字段的属性别名
   * @param rowBounds  返回结果的大小控制
   * @param <K>
   * @param <V>
   * @return
   */
  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
        configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
    final DefaultResultContext<V> context = new DefaultResultContext<V>();
    for (V o : list) {
      context.nextResultObject(o);
      mapResultHandler.handleResult(context);
    }
    return mapResultHandler.getMappedResults();
  }
  
  /**
   * 游标查询
   * @param <T>
   * @return
   */
  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
      registerCursor(cursor);
      return cursor;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * @param statement 唯一标识匹配的语句
   * @param parameter 查询参数
   * @param rowBounds  返回结果的大小控制
   * @param handler 外部结果处理器
   */
  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * 增加
   * @return
   */
  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

  /**
   * 修改
   * @return
   */
  @Override
  public int update(String statement) {
    return update(statement, null);
  }

  /**
   * 增删改公用方法
   * @param statement 唯一标识匹配的执行语句
   * @param parameter 参数
   * @return 返回影响的行数
   */
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 删除
   * @return
   */
  @Override
  public int delete(String statement) {
    return update(statement, null);
  }
  
   /**
   * 提交
   * @param force forces connection commit
   */
  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * 回滚
   * @param force forces connection rollback
   */
  @Override
  public void rollback(boolean force) {
    try {
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 提交批处理执行
   * @return 批处理提交更新记录
   */
  @Override
  public List<BatchResult> flushStatements() {
    try {
      return executor.flushStatements();
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 关闭
   */
  @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * 获取Mapper
   * @param type Mapper对应的Class类型
   * @param <T>
   * @return
   */
  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
  
  // 省略其他代码
  
}
  

3.2 SqlSessionManager source code analysis

First look at the class diagram:

View larger image

image

It can be seen from the figure that SqlSessionManager implements the SqlSessionFactory interface and encapsulates the DefaultSqlSessionFactory
code as follows:

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  //省略其他代码
  
  public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionManager(sqlSessionFactory);
  }
}

  

There are two main differences between SqlSessionManager and DefaultSqlSessionFactory:

  1. SqlSessionManager creates a local thread variable locally, ThreadLocal<SqlSession> localSqlSession. Whenever a SqlSession instance is obtained through startManagedSession(), it will be saved to the SqlSession local thread variable.
public void startManagedSession() {
    this.localSqlSession.set(openSession());
  }
 
 @Override
  public SqlSession openSession() {
    return sqlSessionFactory.openSession();
  }
  

In DefaultSqlSessionFactory each openSession will generate a new DefaultSqlSession

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    try {
      //新建DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
    } finally {
    }
  }

For more detailed source code refer to the next section: DefaultSqlSessionFactory source code analysis

  1. SqlSessionManager implements the SqlSession interface, and SqlSessionMananger integrates the functions of SqlSessionFactory and SqlSession. Through SqlSessionManager, developers can ignore the existence of SqlSessionFacotry and directly face Session programming.

SqlSessionManager provides a sqlSessionProxy internally, this sqlSessionProxy provides the implementation of all SqlSession interfaces, and the implementation uses the SqlSession instance saved by the local thread mentioned above.

In this way, by implementing different sql operations in the same thread, the local thread Sqlsession can be reused, avoiding the need to create a new Sqlsession instance for each sql operation implemented by DefaultSqlSessionFactory.

Let's take a concrete look at the implementation of sqlSessionProxy:

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    //创建SqlSession代理对象
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
  
  
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    //使用代理对象执行数据库操作
    return sqlSessionProxy.<T> selectOne(statement, parameter);
  }
  
  private class SqlSessionInterceptor implements InvocationHandler {
    public SqlSessionInterceptor() {
        // Prevent Synthetic Access
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //从本地线程变量中获取SqlSession实例
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        //不为null
        try {
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
        //为null 则打开新连接
        final SqlSession autoSqlSession = openSession();
        try {
          final Object result = method.invoke(autoSqlSession, args);
          autoSqlSession.commit();
          return result;
        } catch (Throwable t) {
          autoSqlSession.rollback();
          throw ExceptionUtil.unwrapThrowable(t);
        } finally {
          autoSqlSession.close();
        }
      }
    }
  }
  
  //省略其他代码
  
}

4. How to create SqlSession

To understand how SqlSession is created, we need to know SqlSessionFactory, which is the SqlSession factory.

View larger image

image

It can be seen from the class diagram that SqlSessionFactory defines DefaultSqlSessionFactory for a specific SqlSession factory
, and implements SqlSessionFactory, which is generated by DefaultSqlSessionFactory

4.1 SqlSessionFactory interface definition

/**
* 通过外部传入的connection 或 database 创建(打开) SqlSession
* 方法重载,通过参数不同创建SqlSession
*/
public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

4.2 DefaultSqlSessionFactory source code analysis

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) { #1
    this.configuration = configuration;
  }

  //省略...

  /**
   * #mark 创建SqlSession
   * @param execType 执行器类型
   * @param level 事务隔离级别
   * @param autoCommit 是否自动提交
   * @return
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { #2
    Transaction tx = null;
    try {
      //传入的configuration获取环境变量对象、Environment可以配置多个环境配置
      final Environment environment = configuration.getEnvironment();
      //从环境对象中获取事务工厂对象
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //根据DataSource、事务隔离级别、自动提交创建事务对象
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //#mark 新建执行者 20170820
      final Executor executor = configuration.newExecutor(tx, execType);
      //#mark 创建默认SqlSession
      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();
    }
  }

  //省略...

  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
  }

  private void closeTransaction(Transaction tx) 
    if (tx != null) {
      try {
        tx.close();
      } catch (SQLException ignore) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

}

Detailed description:

  • Annotation #1 Pass in the Configuration configuration object through the constructor, Configuration is an object that runs through the entire play

  • Note #2 openSessionFromDataSource As the name suggests, open SqlSession from DataSource, call new DefaultSqlSession(configuration, executor, autoCommit) to build SqlSession, see the source code for the specific implementation

Executor and ErrorContext will be introduced in detail later

4.3 How MyBatis performs SqlSession creation

From the previous description, we know that SqlSession is generated by DefaultSqlSessionFactory. Through the IDEA associated search function, we found the specific calling class: SqlSessionFactoryBuilder. SqlSessionFactoryBuilder mainly obtains the configuration input stream and creates an instance of DefaultSqlSessionFactory

First look at the following class diagram:

image

SqlSessionFactoryBuilder source code analysis

/**
 * Builds {@link SqlSession} instances.
 * SqlSession 工厂构造器
 *
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {

  //省略
  
  /**
   * 通过字符流构建
   * @param reader 字符流
   * @param environment 环境变量
   * @param properties 属性配置
   * @return
   */
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) { #1
    try {
      //从字符流中创建XML配置对象
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  //省略
  
  /**
   * 通过字节流构建
   * @param inputStream
   * @param environment
   * @param properties
   * @return
   */
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  
  /**
   * #mark SqlSessionFactory 初始化
   * @param config
   * @return
   */
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}
  • Callout #1 Build SqlSessionFactory object through character stream, all build methods call: new DefaultSqlSessionFactory(config) build

4.4 SqlSession unit test

According to the test specification, we found the test class SqlSessionTest, which has many methods, and I have simplified the required parts.

/**
 * #mark 源码学习入口
 */
public class SqlSessionTest extends BaseDataTest {
  private static SqlSessionFactory sqlMapper;

  @BeforeClass
  public static void setup() throws Exception {
    //初始化数据源,使用内存数据库、运行一次自动销毁
    createBlogDataSource();
    //资源文件地址
    final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
    //获取资源文件字符流
    final Reader reader = Resources.getResourceAsReader(resource);
    //构建 SqlSessionFactory
    sqlMapper = new SqlSessionFactoryBuilder().build(reader);
  }


  /**
   * 测试SqlSession 开启和关闭
   * @throws Exception
   */
  @Test
  public void shouldOpenAndClose() throws Exception {
    SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE);
    session.close();
  }

  /**
   * 测试提交一个未使用的SqlSession
   * @throws Exception
   */
  @Test
  public void shouldCommitAnUnUsedSqlSession() throws Exception {
    SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE);
    session.commit(true);
    session.close();
  }

  /**
   * 测试提交一个未使用的SqlSession
   * @throws Exception
   */
  @Test
  public void shouldRollbackAnUnUsedSqlSession() throws Exception {
    SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE);
    session.rollback(true);
    session.close();
  }

  /**
   * 跟踪一个完整查询
   * 查出所有作者 #20170831
   * @throws Exception
   */
  @Test
  public void shouldSelectAllAuthors() throws Exception {
    SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE);
    try {
      List<Author> authors = session.selectList("org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAllAuthors");
      assertEquals(2, authors.size());
    } finally {
      session.close();
    }
  }
  
  //省略部分代码

@BeforeClass
public static void setup() throws Exception {}
contains the specific SqlSession creation process in this method

5. SqlSession is implemented in Spring integration

5.1 Introduction to SqlSessionFactoryBean

SqlSessionFactoryBean In basic MyBatis, session factories can be created using SqlSessionFactoryBuilder. In MyBatis-Spring, SqlSessionFactoryBean is used instead.
-- official documentation

Then let's download the source code of MyBatis-Spring and see for details .

View larger image

image

SqlSessionFactoryBean implements three important interfaces of Spring:

  • The InitializingBean
    interface is implemented by beans and needs to react when the BeanFactory has set all their properties: for example, perform custom initialization, or just check if all required properties are set.

key code

/**
   * {@inheritDoc}
   */
  @Override
  public void afterPropertiesSet() throws Exception {
    //参数检测
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");
    //sqlSessionFactory 实例化
    this.sqlSessionFactory = buildSqlSessionFactory();
    
  }
  
  /**
   * Build a {@code SqlSessionFactory} instance.
   *
   * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
   * {@code SqlSessionFactory} instance based on an Reader.
   * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
   *
   * @return SqlSessionFactory
   * @throws IOException if loading the config file failed
   */
  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    //省略 configuration 创建代码
    
    //返回创建的SqlSessionFactory 
    return this.sqlSessionFactoryBuilder.build(configuration);
  }
  • FactoryBean
    is used to create complex Bean objects. General beans can be configured through XML files, but it is difficult to use XML for complex Bean objects.

key code

/**
   * {@inheritDoc}
   */
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    //返回创建好的SqlSessionFatory对象
    return this.sqlSessionFactory;
  }
  • ApplicationListener
    Event raised when the ApplicationContext is initialized or refreshed, executed when the Spring container is fully started.

key code

/**
   * {@inheritDoc}
   */
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    if (failFast && event instanceof ContextRefreshedEvent) {
      // fail-fast -> check all statements are completed
      //检测MyBatis所有配置文件语句是否完成
      this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
  }

6. References


The SqlSession about MyBatis source code interpretation is introduced here. If you have any questions, please leave a message, thank you.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324992515&siteId=291194637