StatementHandler


我们接着看四大组件的StatementHandler。

首先还是来看代码

接上一篇末尾

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();
      //在这里初始化了除了Excutor的其他三大组件,注意了,这行代码完成的工作很重要,而Executor对象则在初始化得到DefaultSqlSession对象的时候就已经注入到其中了。
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

从上述代码我们已经知道初始化StatementHandler的时机了,那么下面我们就来看看StatementHandler的构造以及具体实现吧。

StatementHandler介绍

先看看接口的组成

public interface StatementHandler {
//prepare方法是用来编译SQL的
  Statement prepare(Connection connection)
      throws SQLException;
//设置参数化Sql ps.setParameter()

  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();

}

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

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

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

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

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

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

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

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

StatementHandler的初始化

很明显从上述代码中可以看出,StatementHandler是通过

  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

初始化的。

那我们就来看看这行代码的具体实现

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //生成一个StatementHandler实例,StatementHandler有多个实现类,具体调用哪个实现类,通过mappedStatement的相关信息决定,我们后面会具体分析。RoutingStatementHandler只是一个路由信息类。具体的实现类还需要看情况。
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    //这行代码就不多讲了,之前讲Mybatis插件的时候已经讲过了
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

RoutingStatementHandler的构造方法

 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
   //我们实际使用的StatementHandler实例对象有三种,分别对应三种原生JDBC的Statement
   //分别是Statement (通过查询)  PreparedStatement (预编译的Statement 执行参数化查询 数据库系统会对sql语句进行预编译处理(如果JDBC驱动支持的话),预处理语句将被预先编译好,这条预编译的sql查询语句能在将来的查询中重用)和CallableStatement (存储过程)
   //关于PreparedStatement Statement的区别后面会有文章来专门介绍。
    //我们这里根据StatementType,调用的是  delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, 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());
    }

  }

我们接着看PreparedStatementHandler构造方法的具体实现

public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  //这里调用了PreparedStatementHandler的父类BaseStatementHandler的构造方法,上述的三个类都继承了这个类,这个类的构造方法初始化了很多东西,是非常重要的,这里的设计,很明显的使用了模板方法模式。
  super(executor, mappedStatement, parameter, rowBounds, resultHandler, 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;
//初始化parameterHandler和resultSetHandler,就是在这里初始化了剩下的两大组件,而这两大组件都是statementHandler的属性
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
SimpleExecutor的prepareStatement方法

得到handler对象后,方法继续往下执行, stmt = prepareStatement(handler, ms.getStatementLog());

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
  //得到JDBC Connection
    Connection connection = getConnection(statementLog);
  //这里就要到了上面说到的StatementHandler的一个重要方法perpare方法,用来预编译sql
  stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }
  public Statement prepare(Connection connection) throws SQLException {
    return delegate.prepare(connection);
  }
BaseStatementHandler的prepare
  public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement);
      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抽象方法

很明显又是模板方法模式

PreparedStatementHandler 的instantiateStatement
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    //如果需要生成相应的键值,这次Debug并没有要求键值,
    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);
      }
    } 
    //如果在Mapper配置文件中设置了resultSetType,
    else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      //这种就是我们在使用原生JDBC时最常用的方法了
      return connection.prepareStatement(sql);
    }
  }
PreparedStatement prepareStatement(String sql,
                                   int resultSetType,
                                   int resultSetConcurrency)
                                   throws SQLException

其中两个参数的意义是:

resultSetType 是设置 ResultSet 对象的类型可滚动,或者是不可滚动。取值如下: ResultSet.TYPE_FORWARD_ONLY 只能向前滚动

​ ResultSet.TYPE_SCROLL_INSENSITIVE 和 Result.TYPE_SCROLL_SENSITIVE 这两个 方法都能够实现任意的前后滚动,使用各种移动的 ResultSet 指针的方法。二者的区别在于前者对于修改不敏感,而后者对于修改敏感。 resultSetConcurency 是设置 ResultSet 对象能够修改的,取值如下:

​ ResultSet.CONCUR_READ_ONLY 设置为只读类型的参数。

​ ResultSet.CONCUR_UPDATABLE 设置为可修改类型的参数。

所以prepare方法就是完成SQL的预编译,只是把原生JDBC预编译的过程封装了一下,比较容易理解。

handler.parameterize(stmt);

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

RoutingStatementHandler

  public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }

关于具体的实现要涉及到Mybatis的另一个重要组件,接下来会有专门的博客来介绍。

handler.\query(stmt, resultHandler);

当Statement经过stmt = prepareStatement(handler, ms.getStatementLog());

处理后(使用了prepare预编译,使用了parameterize设置参数),

将其传给query(stmt, resultHandler);得到我们想要的返回内容。

 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
    PreparedStatement ps = (PreparedStatement) statement; 
   //执行查询操作
    ps.execute();
   //通过ResultSetHandler来处理结果集
    return resultSetHandler.<E> handleResultSets(ps);  
  }  

这个的具体实现以后会细说,到这里,一次完整的查询就结束了。

通过了四大组件的配合,才完成了这一次最简单的查询。

在得到DefaultSqlSession对象时初始化了Executor执行器。

然后在调用Executor的doQuery方法查询的时候,会初始化StatementHandler对象,

然后在初始化StatementHandler的过程中,会初始化ParameterHandler和ResultSetHandler对象,

生成的StatementHandler对象会保存这两个对象的引用。

直到这时,Executor的职责就差不多完成了,通过其他的三大组件来完成接下来的数据库查询工作并返回结果。

总结

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


当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。

当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。

我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultHandler来改造组装结果。

懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值