MyBatis原理系列(五)-手把手带你了解Statement、StatementHandler、MappedStatement间的关系

MyBatis原理系列(一)-手把手带你阅读MyBatis源码
MyBatis原理系列(二)-手把手带你了解MyBatis的启动流程
MyBatis原理系列(三)-手把手带你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系
MyBatis原理系列(四)-手把手带你了解MyBatis的Executor执行器
MyBatis原理系列(五)-手把手带你了解Statement、StatementHandler、MappedStatement间的关系
MyBatis原理系列(六)-手把手带你了解BoundSql的创建过程
MyBatis原理系列(七)-手把手带你了解如何自定义插件
MyBatis原理系列(八)-手把手带你了解一级缓存和二级缓存
MyBatis原理系列(九)-手把手带你了解MyBatis事务管理机制

在上篇文章中我们介绍了Executor的三种执行器,并且在执行update和query方法的时候,都会有一个Statement和StatementHandler的操作。当时我们一笔带过了,在这篇文章中我们将介绍Statement,StatementHandler的关系,以及它们的实现细节。

1. Statement 对象

我们先来了解下Statement对象,在原始JDBC操作中,会有加载驱动,设置属性,获取连接,创建Statement对象...等一系列操作。Statement对象在JDBC操作中就是向数据库发送sql语句,并获取到执行结果。Statement对象有三种,分别是Statement,PreparedStatement,CallableStatement。它们的继承关系如下

Statement继承关系
  1. Statement:可以发送字符串类型的sql,不支持传递参数。适用于静态sql语句。
  2. PreparedStatement: 预编译的sql语句,接受参数传入,并且可以防止sql注入,提高安全性。Sql语句会编译在数据库系统,适用于多次执行相同的sql,因此性能高于Statement。
  3. CallableStatement:在执行存储过程时使用,并且可以接受参数传入。

Statement 接口方法有这么多,主要就是执行更新,查询,获取结果等操作。

Statement接口方法

2. StatementHandler 对象

2.1 StatementHandler 对象初识

StatementHandler 对象从字面意义上来讲就是管理Statement对象的了。它有两个直接实现,一个是BaseStatementHandler,另一个是RoutingStatementHandler。然后BaseStatementHandler有三个实现分别是SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler,他们分别管理的就是上面讲到的Statement,PreparedStatement和CallableStatement对象了。继承关系如下图,是不是很像Executor的继承关系,BaseStatementHandler是使用了适配器模式,减少了实现接口的复杂性,RoutingStatementHandler则是包装了以上三种Handler,作为一个代理类进行操作。

StatementHandler继承关系

StatementHandler 接口的方法如下

/**
 * @author Clinton Begin
 */
public interface StatementHandler {

  // 创建Statement对象
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  // 对Sql中的占位符进行赋值
  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;
  
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  // 获取BoundSql对象
  BoundSql getBoundSql();

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

}
2.2 StatementHandler 对象创建

StatementHandler 是在哪里创建的呢?在手把手带你了解MyBatis的Executor执行器中,在执行doQuery和doUpdate方法时,都会创建StatementHandler对象。
以SimpleExecutor为例,创建StatementHandler对象实际由Configuration对象创建的。

// SimpleExecutor的doUpdate方法
@Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 1. 创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      // 2. 创建Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 3. 执行sql操作
      return handler.update(stmt);
    } finally {
      // 2. 关闭Statement
      closeStatement(stmt);
    }
  }

Configuration的newStatementHandler方法中,创建的是RoutingStatementHandler对象。我们知道RoutingStatementHandler实际是对三种StatementHandler的一种包装。

// Configuration的newStatementHandler方法
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;
  }

继续点击去看,根据MappedStatement对象的类型,创建出具体的StatementHandler对象。如果MappedStatement没有指出具体的StatementType(),那么StatementType默认是PREPARED类型的。

// 实际的处理器,SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler三种处理器中的一种
  private final StatementHandler delegate;

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

  }

接下来,我们看看PreparedStatementHandler创建的过程,实际调用的是BaseStatementHandler的构造方法。

// PreparedStatementHandler的构造方法
 public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
   // 实际调用的是BaseStatementHandler的构造方法
   super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
 }

至此,我们了解到了,其实三种StatementHandler都是用的BaseStatementHandler的构造方法创建的。

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);
  }
2.3 Statement对象创建

StatementHandler对象创建出来了,就可以创建Statement对象了。也是以SimpleExecutor执行器为例子。

      // 1. 创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      // 2. 创建Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      ...

SimpleExecutor 的 prepareStatement方法 中主要做了以下三步:

  1. 获取数据库连接
  2. 创建Statement对象
  3. 设置sql参数
// SimpleExecutor 的 prepareStatement方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 1. 获取数据库连接
    Connection connection = getConnection(statementLog);
    // 2. 创建Statement对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 3. 设置sql参数
    handler.parameterize(stmt);
    return stmt;
  }

我们继续看看prepare()方法做了什么,这个方法BaseStatementHandler给出了默认实现,因此三个StatementHandler用的都是这个实现。主要做了以下工作

  1. 初始化Statement对象
  2. 设置超时时间
  3. 设置查询大小
  4. 出现异常关闭Statement对象
// BaseStatementHandler的prepare方法
 @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 1. 初始化Statement对象
      statement = instantiateStatement(connection);
      // 2. 设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      // 3. 设置查询大小
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      // 4. 关闭Statement对象
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

可以Statement对象的初始化操作是在instantiateStatement方法中进行的,我们继续看看instantiateStatement这个方法又做了什么操作。好的,在BaseStatementHandler中instantiateStatement方法被设计为抽象方法,由子类实现,这点也体现出了模板方法的设计模式。

// BaseStatementHandler的instantiateStatement方法
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

现在以SimpleStatementHandler为例子,最终调用的还是connection.createStatement()方法,回到了最初的起点,也就是MyBatis对JDBC操作进行了包装。

@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    // 实际还是调用的connection.createStatement()方法
    if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.createStatement();
    } else {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

获取到了Statement 对象,就可以快乐执行execute方法,向数据库发送sql语句执行了。

3. MappedStatement 对象

有眼尖的同学在前面会看到Executor在执行doUpdate的时候,会传入MappedStatement对象,那么MappedStatement和Statement,StatementHandler对象间有什么关联呢。
我们在用MyBatis配置sql的时候,insert/update/delete/select等标签下面都会包含一段sql,MappedStatement就是对sql标签的信息描述。

// 一个select标签会对应一个MappedStatement对象
 <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from t_test_user
    where id = #{id,jdbcType=BIGINT}
  </select>

MappedStatement 类的私有属性如下

 // mapper配置文件名,如:userMapper.xml
  private String resource;
  // 配置类
  private Configuration configuration;
  // 命名空间+标签id 如com.example.demo.dao.TTestUserMapper.selectByPrimaryKey
  private String id;
  private Integer fetchSize;
  // 超时时间
  private Integer timeout;
  // sql对象类型 STATEMENT, PREPARED, CALLABLE 三种之一
  private StatementType statementType;
  // 结果集类型
  private ResultSetType resultSetType;
  // sql语句
  private SqlSource sqlSource;
  // 缓存
  private Cache cache;
  // 参数映射关系
  private ParameterMap parameterMap;
  // 结果映射关系,可以自定义多个ResultMap
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  // 是否启用缓存
  private boolean useCache;
  // 结果是否排序
  private boolean resultOrdered;
  // sql语句类型,INSERT, UPDATE, DELETE, SELECT
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;

其中最主要的方法还是getBoundSql方法,对动态sql进行解析,获取最终的sql语句。关于SqlSource,BoundSql相关的内容我们将在其它文章中介绍。

  public BoundSql getBoundSql(Object parameterObject) {
    // 获取BoundSql对象,BoundSql对象是对动态sql的解析
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 获取参数映射
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

4. 总结

这篇文章带大家详细了解Statement对象,StatementHandler以及其三种实现,还有MappedStatement也做了简单介绍,Statement对象作为主要组件,了解其创建和原理对我们整体了解MyBatis会很有帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容