一篇看懂Mybatis的SqlSession运行原理

671 阅读6分钟

前言

SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似 JDBC生成的Connection对象的SqlSession对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐是使用Mapper接口形式),那么它是如何执行实现的,这就是本篇博文所介绍的东西,其中会涉及到简单的源码讲解。需要详细的mybatis技术资料以及思维脑图可以关注公众号:麒麟改bug。

一、开启一个数据库访问会话---创建SqlSession对象

SqlSession sqlSession = factory.openSession(); 

MyBatis封装了对数据库的访问,把对数据库的会话和事务控制放到了SqlSession对象中

二、为SqlSession传递一个配置的Sql语句

为SqlSession传递一个配置的Sql语句的Statement Id和参数,然后返回结果:

List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);

上述的"com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",是配置在EmployeesMapper.xml 的Statement ID,params是传递的查询参数。

让我们来看一下sqlSession.selectList()方法的定义:

public <E> List<E> selectList(String statement, Object parameter) {  
    return this.selectList(statement, parameter, RowBounds.DEFAULT);  
}  
 
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {  
    try {  
        //1.根据Statement Id,在mybatis 配置对象Configuration中查找和配置文件相对应的MappedStatement      
        MappedStatement ms = configuration.getMappedStatement(statement);  
        //2. 将查询任务委托给MyBatis 的执行器 Executor  
        List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  
        return result;  
    } catch (Exception e) {  
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
    } finally {  
        ErrorContext.instance().reset();  
    }  
} 

MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使用org.apache.ibatis.session.Configuration实例来维护。使用者可以使用sqlSession.getConfiguration()方法来获取。MyBatis的配置文件中配置信息的组织格式和内存中对象的组织格式几乎完全对应的

上述例子中的:

<select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >  
   select   
       EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY  
   from LOUIS.EMPLOYEES  
   <if test="min_salary != null">  
       where SALARY < #{min_salary,jdbcType=DECIMAL}  
   </if>  
</select>

加载到内存中会生成一个对应的MappedStatement对象,然后会以key="com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary" ,value为MappedStatement对象的形式维护到Configuration的一个Map中。当以后需要使用的时候,只需要通过Id值来获取就可以了。

从上述的代码中我们可以看到SqlSession的职能是:SqlSession根据Statement ID, 在mybatis配置对象Configuration中获取到对应的MappedStatement对象,然后调用mybatis执行器来执行具体的操作

三、执行query()方法

  1. MyBatis执行器Executor根据SqlSession传递的参数执行query()方法(由于代码过长,读者只需阅读我注释的地方即可):

    /**

    • BaseExecutor 类部分代码

    */
    public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 1. 根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 2. 为当前的查询创建一个缓存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    @SuppressWarnings("unchecked")
    public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
    }
    List list;
    try {
    queryStack++;
    list = resultHandler == null ? (List) localCache.getObject(key) : null;
    if (list != null) {
    handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
    // 3.缓存中没有值,直接从数据库中读取数据
    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    } finally {
    queryStack--;
    }
    if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
    deferredLoad.load();
    }
    deferredLoads.clear(); // issue #601
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    clearLocalCache(); // issue #482
    }
    }
    return list;
    }

    private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {

          //4. 执行查询,返回List 结果,然后    将查询的结果放入缓存之中  
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);  
      } finally {  
          localCache.removeObject(key);  
      }  
      localCache.putObject(key, list);  
      if (ms.getStatementType() == StatementType.CALLABLE) {  
          localOutputParameterCache.putObject(key, parameter);  
      }  
      return list;  
    

    }

    /** *

    • SimpleExecutor类的doQuery()方法实现

    */
    public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
    Configuration configuration = ms.getConfiguration();
    //5. 根据既有的参数,创建StatementHandler对象来执行查询操作
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    //6. 创建java.Sql.Statement对象,传递给StatementHandler对象
    stmt = prepareStatement(handler, ms.getStatementLog());
    //7. 调用StatementHandler.query()方法,返回List结果集
    return handler.query(stmt, resultHandler);
    } finally {
    closeStatement(stmt);
    }
    }

上述的Executor.query()方法几经转折,最后会创建一个StatementHandler对象,然后将必要的参数传递给StatementHandler,使用StatementHandler来完成对数据库的查询,最终返回List结果集。

四、Executor的功能和作用

从上面的代码中我们可以看出,Executor的功能和作用是:

  1. 根据传递的参数,完成SQL语句的动态解析,生成BoundSql对象,供StatementHandler使用;

  2. 为查询创建缓存,以提高性能;

  3. 创建JDBC的Statement连接对象,传递给StatementHandler对象,返回List查询结果;

  1. StatementHandler对象负责设置Statement对象中的查询参数、处理JDBC返回的resultSet,将resultSet加工为List 集合返回:

接着上面的Executor第六步,看一下:prepareStatement() 方法的实现:

/** 
   * 
   * SimpleExecutor类的doQuery()方法实现 
   * 
   */  
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 
      Statement stmt = null; 
      try { 
          Configuration configuration = ms.getConfiguration(); 
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 
          // 1.准备Statement对象,并设置Statement对象的参数 
          stmt = prepareStatement(handler, ms.getStatementLog()); 
          // 2. StatementHandler执行query()方法,返回List结果 
          return handler.<E>query(stmt, resultHandler); 
      } finally {
          closeStatement(stmt); 
      } 
}  
 
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
      Statement stmt;  
      Connection connection = getConnection(statementLog);  
      stmt = handler.prepare(connection);  
      //对创建的Statement对象设置参数,即设置SQL 语句中 ? 设置为指定的参数  
      handler.parameterize(stmt);  
      return stmt;  
}

五、StatementHandler对象的总结

以上我们可以总结StatementHandler对象主要完成两个工作:

  1. 对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使用的是SQL语句字符串会包含 若干个? 占位符,我们其后再对占位符进行设值。
  2.  StatementHandler通过parameterize(statement)方法对Statement进行设值;
  3.  StatementHandler通过List query(Statement statement, ResultHandler resultHandler)方法来完成执行Statement,和将Statement对象返回的resultSet封装成List; 
  1. StatementHandler 的parameterize(statement) 方法的实现:

    /**

    • StatementHandler 类的parameterize(statement) 方法实现
      */
      public void parameterize(Statement statement) throws SQLException {
      //使用ParameterHandler对象来完成对Statement的设值
      parameterHandler.setParameters((PreparedStatement) statement);
      }

    /** *

    • ParameterHandler类的setParameters(PreparedStatement ps) 实现

    • 对某一个Statement进行设置参数 */
      public void setParameters(PreparedStatement ps) throws SQLException {
      ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
      List parameterMappings = boundSql.getParameterMappings();
      if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() != ParameterMode.OUT) {
      Object value;
      String propertyName = parameterMapping.getProperty();
      if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
      value = boundSql.getAdditionalParameter(propertyName);
      } else if (parameterObject == null) {
      value = null;
      } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
      value = parameterObject;
      } else {
      MetaObject metaObject = configuration.newMetaObject(parameterObject);
      value = metaObject.getValue(propertyName);
      }

               // 每一个Mapping都有一个TypeHandler,根据TypeHandler来对preparedStatement进行设置参数  
               TypeHandler typeHandler = parameterMapping.getTypeHandler();  
               JdbcType jdbcType = parameterMapping.getJdbcType();  
               if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();  
               // 设置参数  
               typeHandler.setParameter(ps, i + 1, value, jdbcType);  
           }  
       }  
      

      }
      }

从上述的代码可以看到,StatementHandler的parameterize(Statement) 方法调用了 ParameterHandler的setParameters(statement) 方法,
ParameterHandler的setParameters(Statement)方法负责 根据我们输入的参数,对statement对象的 ? 占位符处进行赋值。

  1. StatementHandler 的List query(Statement statement, ResultHandler resultHandler)方法的实现:

    /** * PreParedStatement类的query方法实现 */
    public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    //1.调用preparedStatemnt。execute()方法,然后将resultSet交给ResultSetHandler处理
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //2. 使用ResultHandler来处理ResultSet
    return resultSetHandler. handleResultSets(ps);
    }

从上述代码我们可以看出,StatementHandler 的List query(Statement statement, ResultHandler resultHandler)方法的实现,是调用了ResultSetHandler的handleResultSets(Statement) 方法。ResultSetHandler的handleResultSets(Statement) 方法会将Statement语句执行后生成的resultSet 结果集转换成List 结果集

/**   
   * ResultSetHandler类的handleResultSets()方法实现 
   *  
   */  
public List<Object> handleResultSets(Statement stmt) throws SQLException {  
      final List<Object> multipleResults = new ArrayList<Object>();  
 
      int resultSetCount = 0;  
      ResultSetWrapper rsw = getFirstResultSet(stmt);  
 
      List<ResultMap> resultMaps = mappedStatement.getResultMaps();  
      int resultMapCount = resultMaps.size();  
      validateResultMapsCount(rsw, resultMapCount);  
     
      while (rsw != null && resultMapCount > resultSetCount) {  
          ResultMap resultMap = resultMaps.get(resultSetCount);  
       
          //将resultSet  
          handleResultSet(rsw, resultMap, multipleResults, null);  
          rsw = getNextResultSet(stmt);  
          cleanUpAfterHandlingResultSet();  
          resultSetCount++;  
      }
 
      String[] resultSets = mappedStatement.getResulSets();  
      if (resultSets != null) {  
          while (rsw != null && resultSetCount < resultSets.length) {  
              ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);  
              if (parentMapping != null) {  
                  String nestedResultMapId = parentMapping.getNestedResultMapId();  
                  ResultMap resultMap = configuration.getResultMap(nestedResultMapId);  
                  handleResultSet(rsw, resultMap, null, parentMapping);  
              }  
              rsw = getNextResultSet(stmt);  
              cleanUpAfterHandlingResultSet();  
              resultSetCount++;  
          }  
      }  
 
      return collapseSingleResultList(multipleResults);  
}  

文章到这里就结束了

喜欢小编分享的技术文章可以点赞关注哦!

小编这边整理了一份mybatis的思维导图,需要了解的小伙伴可以看看

以及整理好的MyBatis源码分析300多页的技术文档集锦,深入浅出MyBatis技术原理与实战资料集锦200多页,这些都是整理好的资料。关注公众号:麒麟改bug