Mybatis的SqlSession运行原理

前言

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

  了解 SqlSession 的运作原理是学习 Mybatis 插件的必经之路,因为 Mybatis 的插件会在 SqlSession 运行过程中“插入”运行,如果没有很好理解的话,Mybatis 插件可能会覆盖相应的源码造成严重的问题。鉴于此,本篇博文尽量详细介绍 SqlSession 运作原理!

  建议:在我之前的博文《Mybatis 缓存(1)-------- 系统缓存及简单配置介绍》中介绍到 SqlSession 的产生过程,可以先理解后再读此博文可能会更加好理解!

  注:本篇博文也是我最近真正理解 Mybatis 才开始编写的,可能有些地方不太准确,如果有错误之处敬请指出,另外创作不易,望转载告之,谢谢!

  参数资料:《深入浅出 Mybatis 基础原理与实践》(我这里只有电子版 PDF,需要的朋友可以联系我)

 


 

1、SqlSession 简单介绍

  (1)SqlSession 简单原理介绍

  SqlSession 提供 select/insert/update/delete 方法,在旧版本中使用使用 SqlSession 接口的这些方法,但是新版的 Mybatis 中就会建议使用 Mapper 接口的方法。

  映射器其实就是一个动态代理对象,进入到 MapperMethod 的execute 方法就能简单找到 SqlSession 的删除、更新、查询、选择方法,从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了 SqlSession 的接口方法(getMapper() 方法等到 Mapper)执行 SQL 查询(也就是说 Mapper 接口方法的实现底层还是采用 SqlSession 接口方法实现的)。

  注:以上虽然只是简单的描述,但实际上源码相对复杂,下面将结合源码进行简单的介绍!

  (2)SqlSession 重要的四个对象

    1)Execute:调度执行 StatementHandler、ParmmeterHandler、ResultHandler 执行相应的 SQL 语句;

    2)StatementHandler:使用数据库中 Statement(PrepareStatement)执行操作,即底层是封装好了的 prepareStatement;

    3)ParammeterHandler:处理 SQL 参数;

    4)ResultHandler:结果集 ResultSet 封装处理返回。

2、SqlSession 四大对象

(1)Execute 执行器:

  执行器起到至关重要的作用,它是真正执行 Java 与数据库交互的东西,参与了整个 SQL 查询执行过程中。

1)主要有三种执行器:简易执行器SIMPLE(不配置就是默认执行器REUSE是一种重用预处理语句、BATCH批量更新、批量专用处理器

package org.apache.ibatis.session;

/**

  • @author Clinton Begin
    */
    public enum ExecutorType {
    SIMPLE, REUSE, BATCH
    }

2)执行器作用:Executor会先调用StatementHandlerprepare()方法预编译SQL语句,同时设置一些基本的运行参数,然后调用 StatementHandler 的parameterize()方法(实际上是启用了 ParameterHandler 设置参数)设置参数,resultHandler 再组装查询结果返回调用者完成一次查询完成预编译,简单总结起来就是即先预编译SQL语句,之后设置参数(跟 JDBC 的 prepareStatement 过程类似)最后如果有查询结果就会组装返回。

首先,以 SimpleExecutor 为例,查看源码我们得到如下几点重要知识点:

第一:Executor 通过 Configuration 对象中 newExecutor() 方法中选择相应的执行器生成

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    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 (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  } 

(注:最后 interceptorChain.pluginAll() 中执行层层动态代理,最后在可以在调用真正的 Executor 前可以修改插件代码,这也就是为什么学会 Mybatis 的插件必须要知道 SqlSession 的运行过程)

第二:在执行器中 StatementHandler 是根据 Configuration 构建的

public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt
= null;
try {
Configuration configuration
= ms.getConfiguration();
StatementHandler handler
= configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt
= prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}
finally {
closeStatement(stmt);
}
}

第三:Executor 会执行 StatementHandler 的 prepare()方法进行预编译 ----> 填入 connection 对象等参数 ----> 再调用 parameterize() 方法设置参数 ----> 完成预编译

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  } 

  总结以上绘制简单思维图如下:

      

 

 

(2)StatementHanlder 数据库会话器

      1)作用:简单来说就是专门处理数据库会话。详细来说就是进行预编译并且调用 ParameterHandler 的 setParameters() 方法设置参数

      2)数据库会话器主要有三种:SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler,分别对应 Executor 的三种执行器(SIMPLE、REUSE、BATCH)

      我们从上述 Executor 的 prepareStatement()方法中调用了 StatementHandler 的 parameterize() 开始一步步地查看源码,如下得到几点重要的知识点:

      第一:StatementHandler 的生成是由 Configuration 方法中 newStatementHandler() 方法生成的,但是正在创建的是实现了 StatementHandler 接口的 RoutingStatementHandler 对象

 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
                               RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler
= new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }

       第二:RoutingStatementHandler 的通过适配器模式找到对应(根据上下文)的 StatementHandler执行的,并且有SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler,分别对应 Executor 的三种执行器(SIMPLE、REUSE、BATCH)

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (ms.getStatementType()) {
  </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> STATEMENT:
    delegate </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
  </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> PREPARED:
    delegate </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
  </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> CALLABLE:
    delegate </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
  </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
    </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ExecutorException("Unknown statement type: " +<span style="color: rgba(0, 0, 0, 1)"> ms.getStatementType());
}

       之后主要以 PrepareStatementHandler 为例,我们观察到:它是实现 BaseStatementHandler 接口的,最后 BaseStatementHandler 又是实现 StatementHandler 接口的

public class PreparedStatementHandler extends BaseStatementHandler
......
public abstract class BaseStatementHandler implements StatementHandler

      它主要有三种方法:prepareparameterizequery我们查看源码:

      第三:在 BaseStatementHandler 中重写 prepare()方法,instantiateStatement() 方法完成预编译,之后设置一些基础配置(获取最大行数,超时)

@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {closeStatement(statement);
      throw e;
    } catch (Exception e) {closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause:" + e, e);}
  }

      第四:instantiateStatement()预编译实际上也是使用了 JDBC 的 prepareStatement() 完成预编译

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

      第五:在 prepareStatement 中重写 parameterize() 方法。prepare() 预编译完成之后,Executor 会调用 parameterize() 方法(在上面的 Executor 部分中已经做了介绍),实际上是调用 ParameterHandler 的 setParameters() 方法

 @Override
  public void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}

  (3)ParameterHandler 参数处理器

      作用:对预编译中参数进行设置,如果有配置 typeHandler,自然会对注册的 typeHandler 对参数进行处理

      查看并学习源码,得到以下几点重要知识点:

      第一:Mybatis 提供了 ParamterHandler 的默认实现类 DefalutParameterHandler

      

public interface ParameterHandler {

Object getParameterObject();

void setParameters(PreparedStatement ps)
throws SQLException;

}

      (其中:getParameterObject 是返回参数对象,setParameters() 是设置预编译参数)

      第二:从 parameterObject 中取到参数,然后使用 typeHandler(注册在 Configuration 中)进行参数处理:

  @Override
  public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> 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);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();}
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping:" + parameterMapping + ". Cause:" + e, e);} catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping:" + parameterMapping + ". Cause:" + e, e);}
        }
      }
    }
  }

  (4)ResultSetHandler 结果集处理器

      作用:很简单,就是组装结果返回结果集

    第一:ResultSetHandler 接口,handlerResultSets()是包装并返回结果集的,handleOutputParameters() 是处理存储过程输出参数的

public interface ResultSetHandler {

<E> List<E> handleResultSets(Statement stmt) throws SQLException;

<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

void handleOutputParameters(CallableStatement cs) throws SQLException;

    第二:Mybatis 提供了默认的 ResultSetHandler 实现类 DefaultResultSetHandler,其中重点是handlerResultSets()的实现,但是其实现过程比较复杂,这里不过多介绍(emmmmm.... 个人目前能力还达理解,仍需努力)

    

    第三:在 Executor 中 doQuery() 方法返回了封装的结果集

 @Override
  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);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {closeStatement(stmt);
    }
  }

    第四:实际上是返回结果是调用了 resultSetHandler 的 handleResultSets() 方法

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

3、SqlSession 运行总结

(1)文字总结

 SqlSession 的运行主要是依靠 Executor 执行器调用(调度)StatementHandler、parameterHanlder、ResultSetHandler,Executor 首先通过创建 StamentHandler 执行预编译并设置参数运行,而整个过程需要如下几步才能完成:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

  1)prepare 预编译 SQL

    由适配模式生成的 RoutingStatementHandler 根据上下文选择生成三种相应的 XXXStatementHandler;

    在生成的 XXXStatementHandler 内部 instantiateStatement()方法执行底层 JDBC 的 prepareStatement() 方法完成预编译

  2)parameterize 设置参数

    默认是 DefaultParameterHandler(实现了 parameterHandler 接口)中 setParameter() 方法完成参数配置,其中参数从 ParameterObject 中取出,交给 typeHandler 处理

  3)doUpdate/doQuery 执行 SQL

    返回的结果通过默认的 DefaultResultSetHandler(实现了ResultSetHandler接口)封装

(2)运行图总结

       1)SqlSession 内部总运行图

                                       

       2)prepare()方法运行图:                                  3)parameterize() 方法运行图