Mybatis源码学习(25)-StatementHandler解析

导读:本篇文章讲解 Mybatis源码学习(25)-StatementHandler解析,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、简介

  StatementHandler是Mybatis最核心的接口之一, 他完成了Mybatis和数据库交互的相关工作(Mybatis与JDBC之间关于Statement的交互工作)。StatementHandler主要作用:

  • 创建Statement对象
  • 为SQL 语句绑定实参
  • 执行select、insert 、update 、delete 等多种类型的SQL语句
  • 批量执行SQL语句
  • 结果集映射成结果对象

StatementHandler接口定义的方法如下:

public interface StatementHandler {
	/**
	 * 从连接中获取一个Statement
	 * @param connection
	 * @param transactionTimeout
	 * @return
	 * @throws SQLException
	 */
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  /**
   * 绑定statement执行时所需的实参
   * @param statement
   * @throws SQLException
   */
  void parameterize(Statement statement)
      throws SQLException;
  /**
   * 批量执行SQL语句
   * @param statement
   * @throws SQLException
   */
  void batch(Statement statement)
      throws SQLException;
  /**
   * 执行update/insert/delete语句
   * @param statement
   * @return
   * @throws SQLException
   */
  int update(Statement statement)
      throws SQLException;
  /**
   * 执行select语句
   * @param <E>
   * @param statement
   * @param resultHandler
   * @return
   * @throws SQLException
   */
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  /**
   * 执行select语句
   * @param <E>
   * @param statement
   * @return
   * @throws SQLException
   */
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
  /**
   * 获取记录SQL语句对应的BoundSql对象
   * @return
   */
  BoundSql getBoundSql();
  /**
   * 获取使用的ParameterHandler对象
   * @return
   */
  ParameterHandler getParameterHandler();

}
二、StatementHandler接口及其实现类

  在Mybatis中,StatementHandler接口提供了一个抽象类BaseStatementHandler,一个直接实现类RoutingStatementHandler,而抽象类BaseStatementHandler又有三个实现子类,分别是:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler等。每个实现类的作用如下:

  • BaseStatementHandler 抽象类,主要实现了公共的方法
  • SimpleStatementHandler 实现类,主要对应JDBC中的Statement接口,用于简单SQL的处理;
  • PreparedStatementHandler 实现类,主要对应JDBC中的PreparedStatement,处理预编译SQL的接口;
  • CallableStatementHandler 实现类,主要对应JDBC中CallableStatement,用于执行存储过程相关的接口;
  • RoutingStatementHandler 直接实现类,上述三个实现类的路由类,没有实际操作,只是负责上面三个StatementHandler的创建及调用。

StatementHandler接口的类图:
在这里插入图片描述

三、实现类RoutingStatementHandler

  RoutingStatementHandler在StatementHandler的结构体系中,扮演者路由器的角色。
RoutingStatementHandler会根据MappedStatement中指定的statementType字段,创建对应
的StatementHandler接口实现。具体实现如下:

public class RoutingStatementHandler implements StatementHandler {
  /**
   * 真正StatementHandler的实例对象
   */
  private final StatementHandler delegate;
  /**
   * 根据StatementType类型,选择创建真正的StatementHandler实例,并赋值给变量delegate
   * @param executor
   * @param ms
   * @param parameter
   * @param rowBounds
   * @param resultHandler
   * @param boundSql
   */
  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());
    }

  }
  
  //下面的方法都通过真正的StatementHandler对象实现
  ……
  
}
四、抽象类BaseStatementHandler

  抽象类BaseStatementHandler是SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler三个实现类的父类,主要实现了一些公共的方法。

1、变量

在BaseStatementHandler中定义的变量及其含义,如下所示:

//BaseStatementHandler.java
/**
   * 全局变量,Mybatis的核心配置对象
   */
  protected final Configuration configuration;
  /**
   * 工具类,用来创建对象,通过configuration获取该实例对象。
   */
  protected final ObjectFactory objectFactory;
  /**
   * 类型处理器的注册器,用来获取符合条件的类型处理器,通过configuration获取该实例对象。
   */
  protected final TypeHandlerRegistry typeHandlerRegistry;
  /**
   * 记录使用的ResultSetHandler对象, 它的主要功能是将结果集映射成结果对象
   */
  protected final ResultSetHandler resultSetHandler;
  /**
   * 记录使用的ParameterHandler对象, ParameterHandler的主要功能是为SQL语句绑定实参,也就是
   * 使用传入的实参替换SQL语句的中“?”占位符
   */
  protected final ParameterHandler parameterHandler;
  /**
   * 记录执行SQL语句的Executor对象
   */
  protected final Executor executor;
  /**
   * 记录SQL语句对应的MappedStatement对象
   */
  protected final MappedStatement mappedStatement;
  /**
   * RowBounds记录了用户设置的offset和limit ,用于在结果集中定位映射的起始位置和结束位置
   */
  protected final RowBounds rowBounds;
  /**
   * 记录SQL语句对应的BoundSql对象
   */
  protected BoundSql boundSql;
2、构造函数

  在构造函数中,除了通过构造函数的参数进行初始的字段外,还有通过全局变量configuration初始化的参数,比如typeHandlerRegistry和objectFactory是在配置文件初始化的时候就解析并存储到全局变量configuration中的,还有parameterHandler变量是通过configuration的newParameterHandler()方法完成了初始化,resultSetHandler变量是通过configuration的newResultSetHandler()方法完成了初始化。除此之外,当boundSql参数为空时,首先通过generateKeys()方法,实现初始化SQL的主键,然后再根据mappedStatement.getBoundSql()方法,获取对应的boundSql实例。

//BaseStatementHandler.java
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
      //调用KeyGenerator.processBefore()方法初始化SQL语句的主键
      generateKeys(parameterObject);
      //根据参数,初始化boundSql变量
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
3、prepare()方法

  prepare()方法用来实现根据Connection连接获取对应的Statement实例对象。在该方法中,首先通过调用instantiateStatement()抽象方法初始化Statement对象,然后再通过setStatementTimeout()设置超时时间,
setFetchSize()方法设置fetchSize等参数。其中,instantiateStatement()抽象方法,是由BaseStatementHandler抽象定义的,并交由子类实现的,这样初始化Statement对象的操作就可以各个子类根据自身要求进行创建,而设置参数相关通用配置,由抽象类(父类)统一完成。

//BaseStatementHandler.java
@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //调用instantiateStatement()抽象方法初始化java.sql.Statement对象
      statement = instantiateStatement(connection);
      //设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      //设置fetchSize参数等
      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);
    }
  }
//BaseStatementHandler.java
 protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
    Integer queryTimeout = null;
    if (mappedStatement.getTimeout() != null) {
      queryTimeout = mappedStatement.getTimeout();
    } else if (configuration.getDefaultStatementTimeout() != null) {
      queryTimeout = configuration.getDefaultStatementTimeout();
    }
    if (queryTimeout != null) {
      stmt.setQueryTimeout(queryTimeout);
    }
    StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
  }
//BaseStatementHandler.java
protected void setFetchSize(Statement stmt) throws SQLException {
    Integer fetchSize = mappedStatement.getFetchSize();
    if (fetchSize != null) {
      stmt.setFetchSize(fetchSize);
      return;
    }
    Integer defaultFetchSize = configuration.getDefaultFetchSize();
    if (defaultFetchSize != null) {
      stmt.setFetchSize(defaultFetchSize);
    }
  }
五、实现类SimpleStatementHandler

  实现类SimpleStatementHandler主要对应JDBC中的Statement接口,用于简单SQL的处理。

  1. parameterize()方法
    空方法,没有做任何处理。
  2. instantiateStatement()方法
    在该方法中,通过connection的createStatement()方法实现了Statement对象的实例化。
//SimpleStatementHandler.java
@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() != null) {
    	//设置结果集是否可以滚动及其游标是否可以上下移动,设置结果集是否可更新
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.createStatement();
    }
  }
  1. query()、queryCursor()方法
    query()、queryCursor()两个方法,逻辑相似。都是首先获取需要执行的SQL语句,然后通过statement.execute()方法执行SQL,最后再通过resultSetHandler的handleResultSets()方法或handleCursorResultSets()方法处理结果集。
//SimpleStatementHandler.java
 @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    //调用Statement.executor()方法执行SQL语句
    statement.execute(sql);
    //将结果集映射成结果对象
    return resultSetHandler.<E>handleResultSets(statement);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleCursorResultSets(statement);
  }
  1. batch()方法
//SimpleStatementHandler.java
 @Override
  public void batch(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.addBatch(sql);
  }
  1. update()方法
    该方法主要用来完成insert、update、delete等SQL语句的执行。该方法需要处理生成主键的相关逻辑,首先statement.execute()方法,然后根据keyGenerator的类型,执行对应的processAfter()方法。
//SimpleStatementHandler.java
@Override
  public int update(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
    	//调用Statement.executor()方法执行SQL语句
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      rows = statement.getUpdateCount();
      //将数据库生成的主键添加到parameterObject中
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      statement.execute(sql);
      rows = statement.getUpdateCount();
      //执行<selectKey>节点中配置的SQL语句获取数据库生成的主键,并添加到parameterObject中
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }
六、实现类PreparedStatementHandler

  PreparedStatementHandler实现类,主要对应JDBC中的PreparedStatement,处理预编译SQL。

  1. parameterize()方法
    通过ParameterHandler.setParameters()方法完成SQL语句的参数绑定。
//PreparedStatementHandler.java
@Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
  1. instantiateStatement()方法
    直接调用JDBC Connection的prepareStatement()方法,创建PreparedStatement对象。当mappedStatement.getKeyGenerator()返回的类型为Jdbc3KeyGenerator时,根据keyColumnNames包含的字段来初始化PreparedStatement对象。
//PreparedStatementHandler.java
@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 {
    	  //在insert语句执行完成之后,会将keyColumnNames指定的列返回
        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);
    }
  }
  1. query()、queryCursor()方法
    query()、queryCursor()两个方法,逻辑相似。都是首先把参数statement转换成PreparedStatement类型,然后执行ps.execute()方法,最后再通过resultSetHandler的handleResultSets()方法或handleCursorResultSets()方法处理结果集。
//PreparedStatementHandler.java
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleCursorResultSets(ps);
  }
  1. batch()方法
//PreparedStatementHandler.java
@Override
  public void batch(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.addBatch();
  }
  1. update()方法
    该方法主要用来完成insert、update、delete等SQL语句的执行。首先把statement类型强转成PreparedStatement,然后执行ps.execute()方法,然后根据参数,执行keyGenerator.processAfter()处理生成主键的相关逻辑。
//PreparedStatementHandler.java
@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;
  }
七、实现类CallableStatementHandler

  CallableStatementHandler 实现类,主要对应JDBC中CallableStatement,用于执行存储过程相关的接口。该类和实现类PreparedStatementHandler逻辑类似,这里不再重复。只是分析一下特殊的parameterize()方法及其调用的registerOutputParameters()方法。

  1. parameterize()方法
    在parameterize()方法中,首先通过registerOutputParameters()方法处理statement参数,然后在调用parameterHandler.setParameters()方法进行参数设置。
//CallableStatementHandler.java
  @Override
  public void parameterize(Statement statement) throws SQLException {
    registerOutputParameters((CallableStatement) statement);
    parameterHandler.setParameters((CallableStatement) statement);
  }
  1. registerOutputParameters()方法
    处理存储过程的输出参数。在后续介绍parameterHandler和resultSetHandler相关内容时,会详细介绍。
//CallableStatementHandler.java
private void registerOutputParameters(CallableStatement cs) throws SQLException {
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    for (int i = 0, n = parameterMappings.size(); i < n; i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
        if (null == parameterMapping.getJdbcType()) {
          throw new ExecutorException("The JDBC Type must be specified for output parameter.  Parameter: " + parameterMapping.getProperty());
        } else {
          if (parameterMapping.getNumericScale() != null && (parameterMapping.getJdbcType() == JdbcType.NUMERIC || parameterMapping.getJdbcType() == JdbcType.DECIMAL)) {
            cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getNumericScale());
          } else {
            if (parameterMapping.getJdbcTypeName() == null) {
              cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE);
            } else {
              cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getJdbcTypeName());
            }
          }
        }
      }
    }
  }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68873.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!