ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2020-06-26 17:39:53  阅读:340  来源: 互联网

标签:null java mappedStatement value 源码 boundSql resultMap statement rsw


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的基本信息, 以及需要替换的参数信息
在这里插入图片描述
在生成最终的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中的原始数据
在这里插入图片描述
在这里插入图片描述
可以看见我这条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);
  }

在这里插入图片描述
结果集的处理方法

  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其实可扩展性比较差, 因为看了这么多方法几乎没有面向接口编程

标签:null,java,mappedStatement,value,源码,boundSql,resultMap,statement,rsw
来源: https://blog.csdn.net/Cia_zibo/article/details/106903316

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有