Mybatis源码学习记录(StatementHandler篇)

前言

上一篇文章分析了Executor执行器,最终的数据库操作交给了StatementHandler来做,本文我们将分析Mybatis中的StatementHandler的作用

在分析之前,我们先回顾一下JDBC中Statement/PreparedStatement/CallableStatement接口的知识

如果说Connection是一座联通应用程序和数据库的桥梁,那么Statement/PreparedStatement/CallableStatement就可以理解为来往通行的车辆,只有车辆才能携带物品(数据)数据库和从数据库中带回数据给应用程序

JDBC提供了三种车辆,并各有各的特性和适用场景

  • Statement:不接受参数,适用于运行静态 SQL 语句,提供了三种执行方法(如boolean execute(String SQL), int executeUpdate(String SQL), ResultSet executeQuery(String SQL))且参数都是一个静态的SQL语句(即直接可以在数据库中执行的SQL语句

  • PreparedStatement:继承了Statement接口,并在此基础上增加了动态参数的能力,可计划多次使用同一个SQL语句(注意这里的SQL是带有?符号,数据库不能直接执行的),接口中提供了很多setXXX()方法,来动态的设置最终执行前的SQL语句参数,注意 PreparedStatement的执行方法和Statement是不一样的(如:boolean execute(), int executeUpdate(), ResultSet executeQuery()

  • CallableStatement:访问数据库存储过程的时候使用,也接受动态参数

另外,JDBC的Connection接口中定义了以下重载的方法来分别获取不同的Statement实例

// 创建一个Statement实例,省略其他方法...
Statement createStatement() throws SQLException;

// 创建一个PreparedStatement实例,注意会有一个String类型的预SQL传入(带有?号的那种),省略其他方法...
PreparedStatement prepareStatement(String sql)
        throws SQLException;
        
// 创建一个CallableStatement实例,注意也会有一个String类型的预SQL传入(带有?号的那种),省略其他方法... 
CallableStatement prepareCall(String sql) throws SQLException;

回顾了JDBCStatement之后,我们这里再回过头来看看Mybatis提供的StatementHandler的作用,顾名思义,StatementHandler就是一个Statement的处理器,正是由于底层JDBC有三种Statement,且从其被创建,到参数化(Statement实例不需要),再到执行数据库操作等,各有各的不同,那Mybatis就需要屏蔽掉底层的实现,抽象出一个Statement的处理器接口,即StatementHandler

这里其实有点抽象工厂的味道

StatementHandler.java

抽象出底层JDBC中三种不同的Statement的创建/参数化处理/执行SQL,以及获取BoundSql,ParamterHandler等功能

public interface StatementHandler {
  // 创建Statement实例
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  
  // 参数化Statement实例
  void parameterize(Statement statement)
      throws SQLException;

  // 
  void batch(Statement statement)
      throws SQLException;
  
  // 执行Update ,返回更新成功的记录数
  int update(Statement statement)
      throws SQLException;

  // 执行Query,同时处理返回值
   List query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

   Cursor queryCursor(Statement statement)
      throws SQLException;

  // 获取BoundSql实例
  BoundSql getBoundSql();

  // 获取参数处理器 
  ParameterHandler getParameterHandler();
}

既然接口定义好了,那就需要相应的实现,活总得有人干

  • UML类结构如下


    Mybatis源码学习记录(StatementHandler篇)_第1张图片
    image.png

由上图可知,三种不同实现的Handler会创建三种不同的Statement实例,BaseStatementHandler中会实现接口中的prepare方法以定义三个实现类中的共同部分,真正的实例化Statement的部分由抽象方法instantiateStatement()完成

BaseStatementHandler.java

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;

    // 1. 创建ParameterHandler,并插件化代理对象返回
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    // 2. 创建ResultSetHandler,并插件化代理对象返回
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
  
  // 覆写StatementHandler的prepare方法
  @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);
    }
  }
  
  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
}

我们这里只举一个PreparedStatementHandler为例

PreparedStatementHandler.java

public class PreparedStatementHandler extends BaseStatementHandler {

  // 构造函数
  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  // 真实的实例化Statement方法
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    // 1. 取出BoundSql中的sql,带有?符号的那种
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        // 2. 调用connection的prepareStatement方法获取PreparedStatement实例返回
        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);
    }
  }
}

至此,我们搞清楚了Mybatis中的statement的创建过程,那到了这里我们三种不同的StatementHandler由谁来创建呢?又怎么知道SimpleExecutor的doQuery的第二部中我们到底实例化了哪种StatementHandler实现呢?

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

  // ...
} 

可以看到这里每次调用执行器doQuery方法,都会利用configuration来实例化一个新的StatementHandler的实现,至于问什么用configuration来做,是因为StatementHandler是Mybatis中的四大核心接口之一,是需要提供给开发人员扩展功能

// Configuration类
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 这里new了一个RoutingStatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 注意这里返回了一个被开发人员自定义插件代理后的StatementHandler
    // 有类似调用的地方,都是Mybatis提供给开发人员,自定义插件用的,这里使用了JDK的动态代理,后续会以单独文章分析
    statementHandler = (StatementHandler)
    interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

RoutingStatementHandler.java

其实就是内部持有一个StatementHandler的属性,所有实现接口的方法都调用了内部属性的方法而已

public class RoutingStatementHandler implements StatementHandler {
  // 内部持有一个真正的StatementHandler实例的代理引用
  private final StatementHandler delegate;
  
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 根据MappedStatement的type类型创建不同的StatementHandler实例
    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());
    }

  }
  
  // 覆写接口方法...
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }

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

  // ...
}

总结

至此,整个StatementHandler分析完毕,statementHandler创建之后得到的实际上是一个经过开发人员自定义插件后返回的代理对象,之后就开始调用这个代理对象的prepare方法,parameterize方法,query方法/update方法等完成整个数据库操作

你可能感兴趣的:(Mybatis源码学习记录(StatementHandler篇))