mybatis源码分析-----核心调度对象StatmentHandler

Posted 千寻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis源码分析-----核心调度对象StatmentHandler相关的知识,希望对你有一定的参考价值。

写在前面

  通过上一偏文章,我们知道:mybatis 的插件开发,主要是集中在Executor(执行器),ParameterHandler(参数处理器),ResultSetHandler(结果集处理器),StatementHandler( 语句处理器)。可以称作myBatis核心的四大金刚

  我们知道了mybatis对外暴露的API(SqlSession)的操作,其实是其持有Executor对于底层的操作。也知道了Executor 的设计采用了模板方法模式。

  查看Executor 的源代码发现,它对底层的操作,其实是其持有StatmentHandler对于 底层的操作。

public class SimpleExecutor extends BaseExecutor {
  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
    //更新方法,首先获取StatmentHandler StatementHandler handler
= configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog());
//对于update 方法,只会有parameterHandler 帮忙绑定预编译的sql的参数。①
return handler.update(stmt); } finally { closeStatement(stmt); } } @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();
    //查询方法,首先获取StatmentHandler StatementHandler handler
= configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog());
//对于query方法,parameterHandler 和 resultSetHandler 会前来帮绑定参数和结果集处理 ②
return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog);
//statmentHandler 预编译sql stmt
= handler.prepare(connection);
//statmentHandler 绑定参数 handler.parameterize(stmt);
return stmt; } }

  首先说明本文重点:

  文中将的四大对象是指:executor, statementHandler,parameterHandler,resultSetHandler对象

  讲到statementHandler,毫无疑问它是我们四大对象最重要的一个,它的任务就是和数据库对话。在它这里会使用parameterHandler和ResultSetHandler对象为我们绑定SQL参数和组装最后的结果返回。

 

 

StatmentHandler 接口

  statmentHandler 位于mybatis的org.apache.ibatis.executor.statement 包下面。也可以发现其和executor的关系

public interface StatementHandler {
  //预编译
  Statement prepare(Connection connection) throws SQLException;
  //绑定参数  
  void parameterize(Statement statement) throws SQLException;
  //批量操作
  void batch(Statement statement)  throws SQLException;
  //更新操作
  int update(Statement statement) throws SQLException;
  //查询操作
  <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;

  BoundSql getBoundSql();
  ParameterHandler getParameterHandler();
}

通过对stantmentHandler 接口的结构分析。它和executor的设计几乎一模一样,我可以猜测statmentHandler 也是采用了模板方法模式的设计。

 

 StatmentHandler 的产生

  通过对于Executor 的分析,可以发现executor 产生了一个RoutingStatmentHandler (从名字中可以发现,这是一个路由)

  这里有几个重要的方法,prepare,parameterize和query,update,他们的作用是不一样的。

  在MyBatis实现了statementHandler的有四个类:

  RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据Executor的类型,创建不同的类型StatementHandler。

  SimpleStatementHandler,这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行。

  PreparedStatementHandler 这个用于预编译参数SQL的运行。

  CallableStatementHandler 它将实存储过程的调度。

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

   根据不同的statatmentType 产生具体的statmentHandler实例

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

  在MyBatis中,Configuration对象会采用new RoutingStatementHandler()来生成StatementHandler对象,换句话说我们真正使用的是RoutingStatementHandler对象,然后它会根据Executor的类型去创建对应具体的statementHandler对象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。

  然后利用具体statementHandler的方法完成所需要的功能。那么这个具体的statementHandler是保存在RoutingStatementHandler对象的delegate属性的,所以当我们拦截statementHandler的时候就要常常访问它了。

 

statmentHandler 的执行

  prepare方法:首先prepare方法是用来编译SQL的,让我们看看它的源码实现。这里我们看到了BaseStatementHandler对prepare方法的实现,

  parameterize方法:上面我们在prepare方法里面预编译了SQL。那么我们这个时候希望设置参数。在Statement中我们是使用parameterize方法进行设置参数的。

  让我们看看PreparedStatementHandler中的parameterize方法:

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

  很显然这里很简单是通过parameterHandler来实现的,我们这篇文章只是停留在statementhandler的程度,等我们讲解parameterHandler的时候再来看它如何实现吧,期待一下吧。

  也就是不管是执行,query和update。parameterHandler 都会前来帮忙,帮助参数的绑定。参见①

      对于执行query方法。ResultSetHandler才会前来帮忙,对结果集做处理。参见②

  query/update方法

  我们用了prepare方法预编译了SQL,用了parameterize方法设置参数,那么我们接下来肯定是想执行SQL,而SQL无非是两种:
    一种是进行查询——query,另外就是更新——update。这些方法都很简单,让我们看看PreparedStatementHandler的实现:

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

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

  我们可以看到如果是进行update的,它将会执行生成主键的操作(插入数据要自动生成主键的时候),然后就返回影响行数。
  如果是进行query的就更加简单了,它就是执行SQL语句,然后讲结果使用resultSetHandler去完成我们的结果组装。

    

 

parameterhandler 和 ResultSetHandler

  parameterhandler 和 ResultSetHandler好比StatmentHandler 的左膀右臂。先后完成了参数绑定设置,已经结果集设置。他们的默认实现了是DefaultParameterHandler和DefaultResultSetHandler

  在构造statmentHandler 实例的时候,会同时产生parameterhandler 和 ResultSetHandler实例。并持有他们的对象。

public abstract class BaseStatementHandler implements StatementHandler {

  protected final Configuration configuration;
  protected final ObjectFactory objectFactory;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  protected final ResultSetHandler resultSetHandler;
  protected final ParameterHandler parameterHandler;

  protected final Executor executor;
  protected final MappedStatement mappedStatement;
  protected final RowBounds rowBounds;

  protected BoundSql boundSql;

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
}

 

ParameterHandler 的作用  

  我们知道ParameterHandler是用来设置参数规则的。档StatementHandler使用prepare()方法后,接下来就是使用它来设置参数,让我们看看它的定义:

public interface ParameterHandler {
  Object getParameterObject();
  void setParameters(PreparedStatement ps) throws SQLException;
}

  十分简单getParameterObject()是获取参数的,而setParameters()是设置参数的,相当于对一条sql所有的参数都执行ps.setXXX(value);

   让我们看看它的实现类:DefaultParameterHandler中的方法实现

  @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);
          }
        }
      }
    }
  }

这里重点是setParameters(),首先它读取了ParameterObject参数对象,然后用typeHandler对参数进行设置,而typeHandler里面需要对jdbcType和javaType进行处理,然后就设置参数了。也很好理解。所以当我们使用TypeHandler的时候完全可以控制如何设置SQL参数。

 

ResultSetHandler

  ResultSetHandler要比ParameterHandler复杂得多,对其原理要讲清楚真的很困难,但是我们一般在插件里面使用不多。所以我就不分析太详细了,大致讲讲它的大概原理。让我们看看ResultSetHandler的接口定义:

 

public interface ResultSetHandler {
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;
  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

 

总结

  通过上面几篇我们懂得了SqlSession下得【四大对象】Executor,StatementHandler,ParameterHandler 和 ResultSetHandler之间的主要方法和协作。

  因为掌握好四大对象的基本方法,了解他们的作用是我们插件编写的根本,没有掌握它们你是没有办法准确的去编写你的插件的。

  StatementHandler是MyBatis四大对象里面最重要的对象,它的方法是十分重要的,也是我们插件的基础。

  当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。
  当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。
  我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultSetHandler来改造组装结果。
  懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。

以上是关于mybatis源码分析-----核心调度对象StatmentHandler的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis源码分析三MyBatis的核心对象及其作用

MyBatis源码分析四XML解析与核心对象的构建

MyBatis源码分析四XML解析与核心对象的构建

Mybatis源码分析

MyBatis核心源码深度剖析SQL执行过程

mybatis拦截器源码分析