Example code for automatic mapping of Mybatis result sets

  • 2020-06-03 06:36:35
  • OfStack

When using Mybatis, sometimes we can not define resultMap, but directly in < select > Specifies resultType on the statement. This is where Mybatis's result set auto-mapping is used. Mybatis's auto mapping is on by default, and we can turn it off if we need to (we can also adjust the auto mapping policy).

1 Mybatis result sets are automatically mapped

When using Mybatis, sometimes we can not define resultMap, but directly in < select > Specify resultType on the statement. In this case, Mybatis's result set auto-mapping is used. Mybatis's auto mapping is turned on by default and automatically maps fields not defined in resultMap with the same name to the corresponding property of the return type. Automatic mapping will ignore case, such as the query in query out 1 field is ID, we have a corresponding return type attribute id, and have a setId () method, then also can match with ID id, can be automatically map Mybatis will come out the query result set fields ID corresponding value is assigned to a return type id attributes of objects.

1.1 Source Code Analysis

The logical rules for automatic mapping can be referred to the DefaultResultSetHandler Mybatis source code, the core code is as follows.


private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  List<UnMappedColumAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  boolean foundValues = false;
  if (autoMapping.size() > 0) {
   for (UnMappedColumAutoMapping mapping : autoMapping) {
    final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
    if (value != null || configuration.isCallSettersOnNulls()) {
     if (value != null || !mapping.primitive) {
      metaObject.setValue(mapping.property, value);
     }
     foundValues = true;
    }
   }
  }
  return foundValues;
 }
 private List<UnMappedColumAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  final String mapKey = resultMap.getId() + ":" + columnPrefix;
  List<UnMappedColumAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
  if (autoMapping == null) {
   autoMapping = new ArrayList<UnMappedColumAutoMapping>();
   final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
   for (String columnName : unmappedColumnNames) {
    String propertyName = columnName;
    if (columnPrefix != null && !columnPrefix.isEmpty()) {
     if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
      propertyName = columnName.substring(columnPrefix.length());
     } else {
      continue;
     }
    }
    final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
    if (property != null && metaObject.hasSetter(property)) {
     final Class<?> propertyType = metaObject.getSetterType(property);
     if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
      final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
      autoMapping.add(new UnMappedColumAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
     }
    }
   }
   autoMappingsCache.put(mapKey, autoMapping);
  }
  return autoMapping;
 } 

The following sentence in the createAutomaticMappings() method in the source code above is to get fields that are not mapped in resultMap in the current query result set for automatic mapping. Please refer to the complete source code for details.


 final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);

1.2 the sample

Now suppose we have an User class with id, name, username, email, mobile attributes, and then we have the following query and its corresponding resultMap definition. We can see that we have id, name, query the user_name, email and mobile fields, we only configuration fields in resultMap user_name corresponding is username attribute, other we didn't configuration, but the results of the query object User id, name, username, email and mobile attributes can have value, because they will be Mybatis assignment with automatic mapping strategy.


 <resultMap type="com.elim.learn.mybatis.model.User" id="BaseResult">
   <result column="user_name" property="username"/>
  </resultMap>
  <select id="findById" resultMap="BaseResult" parameterType="java.lang.Long" >
   select id,name,username user_name,email,mobile from t_user where id=#{id}
  </select>

1.3 Automatic mapping strategy

Mybatis's auto mapping policy is on by default, and the default is to automatically map only the non-nested resultMap. This is configured through the Mybatis global configuration autoMappingBehavior parameter. It has three values, NONE, PARTIAL and FULL.

l NONE indicates that automatic mapping is not enabled

l PARTIAL means that only non-nested resultMap are automatically mapped

l FULL means that all resultMap are automatically mapped


    <!--  Automatic mapping type, optional value is NONE , PARTIAL and FULL Reference, AutoMappingBehavior The enumeration  -->
   <setting name="autoMappingBehavior" value="PARTIAL"/>

In addition to the global configuration of whether automatic mapping is enabled, you can also set whether automatic mapping is enabled for a particular resultMap. This is configured through the autoMapping property of resultMap, with optional values of true and false. autoMapping defined on resultMap has a higher priority than the globally configured autoMapping.

1.4 resultType automatic mapping analysis

When specifying the return result of a query, we can specify resultType directly or resultMap, and then specify the actual return type by the type attribute of the specified resultMap. In fact, the bottom layer of Mybatis processes the result set through resultMap. When we specify resultType, Mybatis generates an empty resultMap internally and then specifies its corresponding type as the TYPE of resultType we specify. The reason why the returned result can be automatically mapped to the corresponding property of resultType is the automatic mapping mechanism of Mybatis described above. If we turn off global auto mapping in this case, Mybatis will not be able to auto map and will not get the return result we need. resultType is specified directly as follows.


  <select id="findById" resultType="com.elim.learn.mybatis.model.User" parameterType="java.lang.Long" >
   select id,name,username,email,mobile from t_user where id=#{id}
  </select>

The contents of the ES132en. xml file of Mybatis are parsed by XMLMapperBuilder, and the Mapper statements defined therein (select, insert, etc.) are parsed by XMLStatementBuilder, resulting in an MappedStatement. For the Select statement, the core logic of its corresponding resultMap parsing is as follows. Please refer to the official source code for more information.


private List<ResultMap> getStatementResultMaps(
   String resultMap,
   Class<?> resultType,
   String statementId) {
  resultMap = applyCurrentNamespace(resultMap, true);
  List<ResultMap> resultMaps = new ArrayList<ResultMap>();
  if (resultMap != null) {
   String[] resultMapNames = resultMap.split(",");
   for (String resultMapName : resultMapNames) {
    try {
     resultMaps.add(configuration.getResultMap(resultMapName.trim()));
    } catch (IllegalArgumentException e) {
     throw new IncompleteElementException("Could not find result map " + resultMapName, e);
    }
   }
  } else if (resultType != null) {
   ResultMap inlineResultMap = new ResultMap.Builder(
     configuration,
     statementId + "-Inline",
     resultType,
     new ArrayList<ResultMapping>(),
     null).build();
   resultMaps.add(inlineResultMap);
  }
  return resultMaps;
 }

Related articles: