java源码学习-Mybatis(4)创建statement和结果集生成

Mybatis创建statement和结果集生成

前文:Mybatis(3)执行sql过程

statementHandler

在Mybatis的Configuration类中, 存在下面三个方法, 我们想要生成一个statment就需要通过一个statemeng的处理器

  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

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

在下面这个生成statement的方法中, 我们可以看见在statement里面包含着parameterHandler和resultSetHandler

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

parameterHandler的作用很简单, 首先内部包含着jdbc和java的类型, 在MappedStatement中存放着sql的基本信息, 以及需要替换的参数信息
java源码学习-Mybatis(4)创建statement和结果集生成
在生成最终的sql的时候就需要这个parameterHandler配合处理
resultSetHandler用于sql的查询返回时处理结果

在prepareStatement方法中, 我们可以看见使用handler生成了一个statement, 然后在parameterize方法中设置入参

  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;
  }
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 从boundSql中取得入参名称, 与上面截图中的sqlSource中的一样
    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中获取
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
          	// parameterObject就是我们在mapper中写的入参, 如果有@Param注解的话
          	// 那么key就会是@Param的value
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            // 将propertyName作为key去参数map中获得value
            value = metaObject.getValue(propertyName);
          }
          // 获得propertyName的java类型
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
          	// 将获得的value替换sql中的占位符
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

mateObject的getValue方法, 可以通过上面获得的参数名绑定参数

  public Object getValue(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        return null;
      } else {
        return metaValue.getValue(prop.getChildren());
      }
    } else {
      return objectWrapper.get(prop);
    }
  }

前面获得的typeHandler是object, 这里使用parameter(value)的getClass获得真实类型

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
      throws SQLException {
    TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
    // 在ps的columnMap中插入key为i, value为parameter的数据
    handler.setParameter(ps, i, parameter, jdbcType);
  }

最终这个setParameter会调用PreparedStatement的setNString方法, 而这个方法则会被代理最终执行的是HikariProxyPreparedStatement的setString方法, 而这里我下载不到源码… 所以只能靠猜测

// 将var1位置的** NOT SPECIFIED **替换为var2
public void setString(int var1, String var2) throws SQLException {
        try {
            ((PreparedStatement)super.delegate).setString(var1, var2);
        } catch (SQLException var4) {
            throw this.checkException(var4);
        }
    }

结果集处理

在上一步我们处理完毕了sql语句, 提交给数据库之后, 数据库会返回结果集, 我们需要接着处理这个结果集, 将结果放入javabean

  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);
      // 获取了一个statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 开始用statement的处理器来query
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 这里发起请求, 将sql提交到数据库执行
    ps.execute();
    // 上面执行完毕后开始处理结果
    return resultSetHandler.handleResultSets(ps);
  }

我们先看看数据库返回的在delegate的results中的原始数据
java源码学习-Mybatis(4)创建statement和结果集生成
java源码学习-Mybatis(4)创建statement和结果集生成
可以看见我这条sql语句返回的的确是我这张表中的所有内容, 第二张图是字段名, 他们的value在第一张图中以byte[]的形式存储
所以这里就很清楚了, 我们后续的处理操作就是根据这两张图中的内容建立一个key-value或者直接放入我们的javabean中作为方法的返回值

  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    // rsw的内容截图在下面, 根据截图可以得知, 这里将我们的key提取了出来, 放在columnName中
    ResultSetWrapper rsw = getFirstResultSet(stmt);
	// 获取resultMap, 也就是我们的方法返回值, 我这里定义的是UserDTO
	// 在resultMap的type中的类型就是我们定义的返回类型
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 处理结果集
      // 不论是不是selectOne这里得到的都是一个list
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

java源码学习-Mybatis(4)创建statement和结果集生成
结果集的处理方法

  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 这里生成了一个Object, 内容是UserDTO
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      // 生成一个metaObject用于后续操作处理, 将UserDTO放入metaObject
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      // 这里判断是否开启了驼峰映射, 我这里是开启了的
      if (shouldApplyAutomaticMappings(resultMap, false)) {
      	// 开启驼峰映射后, 获取映射关系, 并且将值放入UserDTO
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    // 这里返回的是注入完所有值后的UserDTO
    return rowValue;
  }
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
      	// 在rsw的resultSet中获得column这个key的值
      	// 就如我上面说的, 在delegate中得到byte[]并且转换后返回
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          // 这里把值用驼峰映射后的key注入到我们的UserDTO中
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }

最后我们再看入口queryFromDatabase方法, 这里面开了mybatis的一级缓存,
对于一级缓存我的理解是, 在高并发的情况下可能会有很多相同的请求, 我们只需要实际去请求数据库一次, 不需要浪费资源, 而这一次的数据库查询出来的结果可以给在这一次查询中产生的新的查询请求使用.
所以我们可以看见在这个方法中, doQuery之前生成了一个缓存, 在doQuery之后把获得数据注入缓存, 很明显就是当一个数据成为热点数据的时候可以用到这个缓存, 减少了查询数据库的次数.
但是我实际上在使用的时候, 连续发送多次请求并未触发缓存, 原因是这个localCache并不是同一个localCache, 因为缓存的生命周期是存在于一个SqlSession中的
查询资料后发现, 想要使用一级缓存需要开启事务, 使用注解@Transaction

  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接收的, 所以我们结果集处理的时候得到的是一个list
      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;
  }

最后queryFromDatabase方法返回的list会回到selectList中, 在selectOne方法中调用了selectList方法, 最后判断这个list的size是不是1, 如果不是则抛出selectOne的but found x异常信息, 如果是则返回结果

后记

mybatis的源码学习差不多就到此结束了, 算是发现了mybatis其实可扩展性比较差, 因为看了这么多方法几乎没有面向接口编程

上一篇:mybatis一文全解


下一篇:mybatis执行流程