tkmybatis-通用mapper查询-和-自定义xml查询-结果封装

tk.mybatis 通用mapper查询 和 自定义xml查询 结果封装

(bug:通用mapper @Column注解 设置不生效,属性封装为null )

程序启动时,会检查所有的mapper方法(包括通用mapper方法和自定义mapper方法),并设置这些mapper方法的sql语句。

程序启动时

通用mapper方法

sql语句通常由在BaseSelectProvider/BaseInsertProvider/…提供,如果返回值为Bean实体,则在BaseXXXProvider中会调用setResultType()方法,修改返回值类型为实体类型。例如

public String selectAll(MappedStatement ms) {
    final Class<?> entityClass = getEntityClass(ms);
    //修改返回值类型为实体类型
    setResultType(ms, entityClass);
    StringBuilder sql = new StringBuilder();
    sql.append(SqlHelper.selectAllColumns(entityClass));
    sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));

    // 逻辑删除的未删除查询条件
    sql.append("<where>");
    sql.append(SqlHelper.whereLogicDelete(entityClass, false));
    sql.append("</where>");

    sql.append(SqlHelper.orderByDefault(entityClass));
    return sql.toString();
}

在该setResultType()方法前的getEntityClass()方法中,会创建并缓存EntityTable,EntityTable会将实体属性字段和数据库字段进行映射。映射逻辑在tk.mybatis.mapper.mapperhelper.resolve#processField方法中

protected void processField(EntityTable entityTable, EntityField field, Config config, Style style) {
        if (field.isAnnotationPresent(Transient.class)) {
            return;
        }
        EntityColumn entityColumn = new EntityColumn(entityTable);
        entityColumn.setUseJavaType(config.isUseJavaType());
        entityColumn.setEntityField(field);
        if (field.isAnnotationPresent(Id.class)) {
            entityColumn.setId(true);
        }
        //1. 实体字段是否使用@Column注解,使用@Column注解value作为数据库字段名
        String columnName = null;
        if (field.isAnnotationPresent(Column.class)) {
            Column column = field.getAnnotation(Column.class);
            columnName = column.name();
            entityColumn.setUpdatable(column.updatable());
            entityColumn.setInsertable(column.insertable());
        }
        if (field.isAnnotationPresent(ColumnType.class)) {
            ColumnType columnType = field.getAnnotation(ColumnType.class);
            entityColumn.setBlob(columnType.isBlob());
            if (StringUtil.isEmpty(columnName) && StringUtil.isNotEmpty(columnType.column())) {
                columnName = columnType.column();
            }
            if (columnType.jdbcType() != JdbcType.UNDEFINED) {
                entityColumn.setJdbcType(columnType.jdbcType());
            }
            if (columnType.typeHandler() != UnknownTypeHandler.class) {
                entityColumn.setTypeHandler(columnType.typeHandler());
            }
        }
        //2. 如果未使用@Column注解,默认使用驼峰转下划线 作为数据库字段名
        if (StringUtil.isEmpty(columnName)) {
            columnName = StringUtil.convertByStyle(field.getName(), style);
        }
        if (StringUtil.isNotEmpty(config.getWrapKeyword()) && SqlReservedWords.containsWord(columnName)) {
            columnName = MessageFormat.format(config.getWrapKeyword(), columnName);
        }
        entityColumn.setProperty(field.getName());
        entityColumn.setColumn(columnName);
        entityColumn.setJavaType(field.getJavaType());
        if (field.getJavaType().isPrimitive()) {
            log.warn("通用 Mapper 警告信息: <[" + entityColumn + "]> 使用了基本类型,基本类型在动态 SQL 中由于存在默认值,因此任何时候都不等于 null,建议修改基本类型为对应的包装类型!");
        }
        processOrderBy(entityTable, field, entityColumn);
        processKeyGenerator(entityTable, field, entityColumn);
        entityTable.getEntityClassColumns().add(entityColumn);
        if (entityColumn.isId()) {
            entityTable.getEntityClassPKColumns().add(entityColumn);
        }
    }

EntityColumn类中,property属性对应实体字段名,column属性对应数据库字段名。EntityTable中都包含了这些信息。

最后将这些信息保存到resultMap的resultMappings中,每个mapper方法对应一个resultMap。

自定义xml 方法

程序启动时,创建SqlSessionFactory,并解析xml文件,将对应的sql映射到mapper方法中,resultMap中保存的映射关系为 xml中resultMap标签对应的信息,如果未定义此标签,则resultMap中resultMappings的为空,result Map中type属性 对应 xml中的select标签中的resultType。

执行查询后

查询会使用到executor.query()方法

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

executor.query()方法使用org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets()方法进行结果封装

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;
    //封装ResultSetWrapper,这是会查询数据库对应的所有字段,添加到columnNames列表中
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = resultMaps.get(resultSetCount);
        //处理结果集的详细过程,真正的处理结果集的地方
        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);
}

handleResultSet(rsw, resultMap, multipleResults, null):执行具体的结果集封装操作

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  try {
    if (parentMapping != null) {
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      if (resultHandler == null) {
        //第一步:如果结果处理器为空,创建一个默认的DefaultResultHandler结果集处理器
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        //第二步:处理每一行的值
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        //第三步:将结果集添加到集合中
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
  } finally {
    // issue #228 (close resultsets)
    closeResultSet(rsw.getResultSet());
  }
}

接下来handleRowValues()方法处理每一行的值的:

//org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  //判断是否有嵌套结果集
  if (resultMap.hasNestedResultMaps()) {
    ensureNoRowBounds();
    checkResultHandler();
    //处理含有嵌套ResultMap的结果
    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  } else {
    //处理不含有嵌套ResultMap的结果  
    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  }
}

以 处理不含有嵌套ResultMap的结果为例

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  //获取ResultSet
  ResultSet resultSet = rsw.getResultSet();
  skipRows(resultSet, rowBounds);
  //主要判断上下文是否已关闭、resultSet是否关闭以及结果集是否还有元素
  while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
     ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
    //获取行数据
    Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  }
}

getRowValue()获取行数据

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  //第一步:通过反射获取到需要封装的结果集实体类的构造方法,然后调用constructor.newInstance()创建一个对象
  //此时返回的rowValue: User{id='null', username='null'} 里面的属性都是null
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
  //行值不为空,并且结果对象有类型处理器
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    //第二步:创建元数据对象MetaObject,方便后面直接设置属性的值
    //将行值包装成元数据对象MetaObject
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      //第三步:驼峰转下划线 自动映射查询出来的数据到前面创建好的Java对象属性中
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
    }
      //第四步:@Column注解 自动映射查询出来的数据到前面创建好的Java对象属性中
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  }
  return rowValue;
}

第三步:applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix)

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) {
      //第二步:根据mapping.column数据库列名,从查询结果集中获取到具体某一列的值
      //底层实际上就是调用的TypeHandler的:rs.getString("id")、rs.getString("name")
      final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
        //第三步:拿到值之后,那就需要动态设置属性的值为刚刚获取到的值,通过metaObject元数据对象直接修改属性的值
        metaObject.setValue(mapping.property, value);
      }
    }
  }
  return foundValues;
}

createAutomaticMappings()

private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    if (autoMapping == null) {
      autoMapping = new ArrayList<>();
        // 获取在程序启动时未映射到实体字段的数据库字段 column
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
      for (String columnName : unmappedColumnNames) {
        String propertyName = columnName;
        if (columnPrefix != null && !columnPrefix.isEmpty()) {
          // When columnPrefix is specified,
          // ignore columns without the prefix.
          if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
            propertyName = columnName.substring(columnPrefix.length());
          } else {
            continue;
          }
        }
         //如果mybatis.configuration.map-underscore-to-camel-case=true,则将数据库字段转成驼峰命名并检查实体中是否包含该属性
         //否则property=null
        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
          //如果存在该属性,并提供了setter方法
        if (property != null && metaObject.hasSetter(property)) {
          if (resultMap.getMappedProperties().contains(property)) {//如果程序启动时的resultMap中包含了该字段的映射
            continue;
          }
          final Class<?> propertyType = metaObject.getSetterType(property);
          if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
            final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
              //将property和column进行映射,此处为 驼峰到下划线的映射
            autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
          } else {
            configuration.getAutoMappingUnknownColumnBehavior()
                .doAction(mappedStatement, columnName, property, propertyType);
          }
        } else {
          configuration.getAutoMappingUnknownColumnBehavior()
              .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
        }
      }
      autoMappingsCache.put(mapKey, autoMapping);
    }
    return autoMapping;
  }

rsw.getUnmappedColumnNames(resultMap, columnPrefix);

public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
    List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
    if (unMappedColumnNames == null) {
        loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
        unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
    }
    return unMappedColumnNames;
}

loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);

private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
    List<String> mappedColumnNames = new ArrayList<>();
    List<String> unmappedColumnNames = new ArrayList<>();
    final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
    // xml方法,resultMap 中的映射关系为xml中resultMap标签定义的值,没定义的话,就为空
    //通用mapper方法,resultMap 中的映射关系为@Column定义的值
    final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
    //columnNames 为数据库对应的所有字段,在第一步封装ResultSetWrapper时创建的
    for (String columnName : columnNames) {
        final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
        //判断程序启动时,保存的resultMap中是否包含该数据库字段映射
        if (mappedColumns.contains(upperColumnName)) {
            mappedColumnNames.add(upperColumnName);
        } else {
            unmappedColumnNames.add(columnName);
            }
        }
    mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
    unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}

第四步:applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix)

  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
      //与第三步不同,这里是获取在程序启动时映射到实体字段的数据库字段 column,即通过@Column注解设置的
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERRED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          //使用属性值setter方法设置属性值,需要注意的是setter方法名称需要为setXXX,XXX为属性名称,例如:setAttachmentName
          //否则可能导致属性值设置错误
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

到这里,Mybatis查询结果集封装的步骤基本就完成了。

注意点

如果使用@Column注解,value必须与数据库字段一致,末尾不允许存在空格!

如果末尾存在空格,例如:

	@Column(name="CUSTOMER_PROVINCE_CITY_DISTRICT ")//末尾多个空格,数据库字段为 CUSTOMER_PROVINCE_CITY_DISTRICT
	String CustomerProvinceCityDistrict ;
  1. 程序启动时,resultMap中建立的映射关系为 CustomerProvinceCityDistrict—>CUSTOMER_PROVINCE_CITY_DISTRICT空格
  2. 在封装结果集时,loadMappedAndUnmappedColumnNames,resultMap中不包含CUSTOMER_PROVINCE_CITY_DISTRICT的映射关系,会将CUSTOMER_PROVINCE_CITY_DISTRICT添加到unMappedColumnNamesMap中,unMappedColumnNamesMap只会在第三步applyAutomaticMappings()中被使用
  3. 在第三步中,applyAutomaticMappings()方法驼峰转下划线自动映射时,又会检查到 resultMap中包含了该驼峰字段的映射,导致无法将数据库字段CUSTOMER_PROVINCE_CITY_DISTRICT映射到实体属性值上
  4. 最终查询出来的结果无法封装到实体上