Mybatis源码阅读笔记五-SQL语句执行过程分析

Mybatis 3.5.5的版本

按照使用方式的不同,我们在创建完成Session之后有三种执行SQL的方式,分别是调用Session接口中预先定义的多种select | insert | update | delete方法、使用mapper代理方式、使用注解方式,那么三种方式是不同的,我们分别进行分析。

本篇主要分析直接调用Session接口中的方法的执行过程(以selectOne方法为例)

一、首先我们看一下在DefaultSqlSession中的方法执行情况:

  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } 
    /*****    省略部分代码      ****/
  }

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

我们看到其实我们调用的selectOne方法实际上也是通过selectList方法得到对象,只不过是取唯一的一个而已。那么selectOne方法中的注释说明了对于List的size不为1的处理方式,0个返回null,多个抛出异常。

在selectList方法中,首先根据我们传入的statement的值来找到对应的MapperStatement,那么我们传入的这个String最好可以是全称(namespace+id)如com.zxzfcsu.getUserById,也可以是这个标签的id值如getUserById。接下来就是调用Executor的query方法进行查询了。其中对parameter执行的操作wrapCollection是为了将数组或者Collection类别的parameter包装成一个Map。ruwBounds参数这是为DEFAULT。

二、Executor中的查询(如上篇四 所述,根据不同的type创建不同的Executor,这里以SimpleExecutor为例)

1.首先我们来看执行的query方法的代码:

//这段代码在BaseExecutor类中
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache();
      }
    }
    return list;
  }

我们分析一下这段代码的逻辑,首先是给MapperedStatement对象传入参数得到BoundSql对象,这个时候SQL已经准备好了。然后准备一个CacheKey对象传递给query方法。

在这个方法中,第一步首先判断queryStack的大小和MapperedStatement对象是否刷新缓存,需要的话就要清除掉缓存。

第二步就是执行我们此次的查询操作。同样对queryStack加1,说明queryStack就是当前查询的任务的数量。然后根据resultHandler是否为空尝试着从缓存中取结果,在默认情况下,我们传入的参数为Executor.NO_RESULT_HANDLER为null。然后根据list是否为null为判断依据,如果不为null,则需要对拿到的list进行处理;如果是null,则需要我们从数据库中查询数据,即调用方法queryFromDatabase。在这之后需要将queryStack减1,说明当前查询完成了。

最后这一步是延迟加载相关的。如果当前的queryStack为0,说明没有任务要执行了,那么这个时候会逐个对延迟加载的对象进行加载。然后判断本地缓存的范围,LocalCacheScope是个枚举类,有两个元素代表两个级别,分别是Session和Statement代表是Session级别和Statement级别,如果是Statement级别到这里就需要清除掉缓存了,因为该MapperedStatement对象的查询已经完成了。

2.那么我们重点关注的自然就是从数据库查询数据的queryFromDatabase方法。我们接下来看一下:

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

这个方法结构非常简单,最主要的查询工作交给了doQuery方法,外面的操作是对缓存的操作。非常有意思的是,该方法在查询开始前,先通过占位符把相应的key-value放进缓存中,查询结束后再进行替换,不知道作者是怎么考虑的,有知道的小伙伴请留言告诉我,谢谢。

3.我们再来看一下doQuery的具体实现(以SimpleExecutor为例)

//这个方法在SimpleExecutor类中
  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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

在这里它创建了一个StatementHandler对象,通过简单的一个代理的方式运作,具体我们创建的类型是根据StatementType来决定的。StatementType是一个枚举类,有三个元素分别是Statement、Prepared和Callable,分别对应的StatementHandler的实现类是Simple、Prepared和Callable,我们默认的是Prepared类型(见下面代码)。

//出自XmlStatementBuilder类parseStatementNode方法
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

然后就是需要通过prepareStatement方法(具体见下面prepareStatement方法代码的分析)生成一个Statement,最后通过StatementHandler来执行query方法进行查询了。

在finally块中最终还是要关闭Statement的。

prepareStatement方法代码的分析:

//准备Statemnet的过程
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

上面准备的过程有几个部分,一是获取数据库连接,这里最终使用的是JdbcTransaction类中的openSession方法,最后交给不同的dataSource去建立连接。二是创建Statement,调用的是handler的prepaere方法,最终会根据不同的StatementType生成不同的java.sql.Statement,那么它是最终直接被jdbc使用的类,这个过程实际上是调用了不同handler的instantiateStatment方法。最后是执行一个parameterize方法对创建的Statement对象设置具体参数。

<1>我们看在PreparedStatementHandler的instantiateStatment方法和parameterize方法的处理过程。

  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 {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

其实这就与原生的jdbc代码很相像了,就是根据不同的返回值、主键字段等创建一个preparedStatement。

<2>我们看一下parameterize方法最终会执行到DefaultParameterHandler中的setParameters方法,最终会根据不同的ParameterMapping中的TypeHandler来进行设置参数,见下面删减后的代码:

  public void setParameters(PreparedStatement ps) {
    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;
            /****   这里有很多代码获取了parameterMapping其中的value值和jdbcType   ****/
          try {
            //最终使用不同的typeHandler去处理值
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

三、StatementHandler中查询的执行(以默认的PreparedStatementHandler类型为例)

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

调用的方法很简单,直接使用PreparedStatement的execute方法,最后是对返回值的处理,因为查询是有结果的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zxzfcsu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值