preface
Using mybatis plus Field type processor , you can easily map data such as arrays and objects directly to entity classes with only one annotation.
@Data @Accessors(chain = true) @TableName(autoResultMap = true) public class User { private Long id; ... /** * be careful!! Mapping annotation must be turned on * * @TableName(autoResultMap = true) * * Either of the following two types of processors can exist at the same time * * be careful!! Select the corresponding JSON processor, and the corresponding JSON parsing dependency package must also exist */ @TableField(typeHandler = JacksonTypeHandler.class) // @TableField(typeHandler = FastjsonTypeHandler.class) private OtherInfo otherInfo; }
This annotation corresponds to the XML that is written as
<result column="other_info" jdbcType="VARCHAR" property="otherInfo" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
The implementation principle can refer to the source code of TableInfo initResultMapIfNeed method:
/** * Automatically build the resultMap and inject it (if the conditions are met) */ void initResultMapIfNeed() { if (autoInitResultMap && null == resultMap) { String id = currentNamespace + DOT + MYBATIS_PLUS + UNDERSCORE + entityType.getSimpleName(); List<ResultMapping> resultMappings = new ArrayList<>(); if (havePK()) { ResultMapping idMapping = new ResultMapping.Builder(configuration, keyProperty, keyColumn, keyType) .flags(Collections.singletonList(ResultFlag.ID)).build(); resultMappings.add(idMapping); } if (CollectionUtils.isNotEmpty(fieldList)) { fieldList.forEach(i -> resultMappings.add(i.getResultMapping(configuration))); } ResultMap resultMap = new ResultMap.Builder(configuration, id, entityType, resultMappings).build(); configuration.addResultMap(resultMap); this.resultMap = id; } }
Existing problems
When autoResultMap=true is used, MP will do the following:
- If the Java type of the field is not a basic type, you will be forced to set the typeHandler property. For example, this is not possible:
/** * IN query */ public static final String IN = "%s IN <foreach item=\"item\" collection=\"%s\" separator=\",\" open=\"(\" close=\")\" index=\"\">#{item}</foreach>"; @TableField(value="code", conditon=IN ) private Strinng[] codes;
- If the fields are complex, such as including functions, with table alias restrictions, etc., the column attribute corresponding to the automatically generated resultMap will become very strange:
@TableField(value = "array_agg(distinct code)",jdbcType = JdbcType.VARCHAR, typeHandler = ArrayTypeHandler.class) private String[] codes;
In the above code, the column attribute of the generated resultMap is rray_agg(distinct code, (one bit is intercepted before and after the value attribute). For specific logic, you can see the MP source code:
TableFieldInfo.java
/** * Get ResultMapping * * @param configuration MybatisConfiguration * @return ResultMapping */ ResultMapping getResultMapping(final Configuration configuration) { ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, StringUtils.getTargetColumn(column), propertyType); TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry(); if (jdbcType != null && jdbcType != JdbcType.UNDEFINED) { builder.jdbcType(jdbcType); } if (typeHandler != null && typeHandler != UnknownTypeHandler.class) { TypeHandler<?> typeHandler = registry.getMappingTypeHandler(this.typeHandler); if (typeHandler == null) { typeHandler = registry.getInstance(propertyType, this.typeHandler); // todo, this will affect registry register(typeHandler); } builder.typeHandler(typeHandler); } return builder.build(); }
StringUtils.java
/** * Verify that the string is a database field */ private static final Pattern P_IS_COLUMN = Pattern.compile("^\\w\\S*[\\w\\d]*$"); /** * Judge whether the string conforms to the naming of database fields * * @param str character string * @return Judgment result */ public static boolean isNotColumnName(String str) { return !P_IS_COLUMN.matcher(str).matches(); } /** * Get the real field name * * @param column Field name * @return Field name */ public static String getTargetColumn(String column) { if (isNotColumnName(column)) { return column.substring(1, column.length() - 1); } return column; }
Yes, rather baffling and make complaints about the open source software in China, which is puzzled by logic and git's questions about developers' questions.
Optimization ideas
In fact, if a field has a typehandler attribute, a ResultMap must be created to handle the type mapping. There is no need to specify autoResultMap=true. However, with this attribute, MP will automatically generate a ResultMap, so that we can generate our own ResultMap without specifying this attribute.
It's not my style to directly modify the official source code. Fortunately, MP provides a custom injection mechanism. Before injecting methods into Mapper, we can generate ResultMap.
Optimization steps
- Create a subclass of the abstract injection method: better autoresultmap java
import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; import com.baomidou.mybatisplus.core.metadata.TableInfo; import lombok.SneakyThrows; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; import org.apache.ibatis.type.UnknownTypeHandler; import java.lang.reflect.Field; import java.util.List; import java.util.stream.Collectors; /** * Optimized AutoResultMap generation strategy * As long as TypeHandler is specified in the field, ResultMap is automatically generated without specifying autoResultMap=true * And only generate ResultMapping for fields with TypeHandler set, not all fields */ public class BetterAutoResultMap extends AbstractMethod { /** * Force resetting the resultMap property of TableInfo */ static Field ResultMapOfTableInfo; /** * Force reset of autoInitResultMap property of TableInfo */ static Field AutoInitResultMapOfTableInfo; static { try { ResultMapOfTableInfo = TableInfo.class.getDeclaredField("resultMap"); ResultMapOfTableInfo.setAccessible(true); AutoInitResultMapOfTableInfo = TableInfo.class.getDeclaredField("autoInitResultMap"); AutoInitResultMapOfTableInfo.setAccessible(true); } catch (NoSuchFieldException e) { e.printStackTrace(); } } /** * Inject custom MappedStatement * * When the entity class does not specify autoResultMap and resultmap, you can use this method to automatically inject resultmap * * @param mapperClass mapper Interface * @param modelClass mapper generic paradigm * @param tableInfo Database table reflection information * @return MappedStatement */ @SneakyThrows @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { if (!tableInfo.isAutoInitResultMap() && tableInfo.getResultMap() == null) { // The ResultMap is automatically generated whenever TypeHandler is specified in the field if (tableInfo.getFieldList().stream() .filter(f -> f.getTypeHandler() != null && f.getTypeHandler() != UnknownTypeHandler.class) .findAny().isPresent()) { // Generate ResultMap ResultMap resultMap = generatorResultMap(tableInfo); configuration.addResultMap(resultMap); // Set the ResultMap property to TableInfo ResultMapOfTableInfo.set(tableInfo, resultMap.getId()); AutoInitResultMapOfTableInfo.set(tableInfo, true); } } return null; } /** * Build resultMap */ ResultMap generatorResultMap(TableInfo tableInfo) { String resultMapId = tableInfo.getCurrentNamespace() + DOT + MYBATIS_PLUS + UNDERSCORE + tableInfo.getEntityType().getSimpleName(); List<ResultMapping> resultMappings = tableInfo.getFieldList().stream().filter(f -> f.getTypeHandler() != null && f.getTypeHandler() != UnknownTypeHandler.class).map(this::getResultMapping).collect(Collectors.toList()); return new ResultMap.Builder(configuration, resultMapId, tableInfo.getEntityType(), resultMappings).build(); } /** * Build resultMapping (only for the fields of typeHandler) * * @param tableFieldInfo * @return */ ResultMapping getResultMapping(TableFieldInfo tableFieldInfo) { ResultMapping.Builder builder = new ResultMapping.Builder(configuration, tableFieldInfo.getProperty(), tableFieldInfo.getProperty(), tableFieldInfo.getPropertyType()); TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry(); JdbcType jdbcType = tableFieldInfo.getJdbcType(); if (jdbcType != null && jdbcType != JdbcType.UNDEFINED) { builder.jdbcType(jdbcType); } TypeHandler<?> typeHandlerMapped = registry.getMappingTypeHandler(tableFieldInfo.getTypeHandler()); if (typeHandlerMapped == null) { typeHandlerMapped = registry.getInstance(tableFieldInfo.getPropertyType(), tableFieldInfo.getTypeHandler()); } builder.typeHandler(typeHandlerMapped); return builder.build(); } }
- Create a custom class for SQL injector: sqlinjectorx java
import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; import com.caspe.base.support.mybatisplus.injector.methods.BetterAutoResultMap; import com.caspe.base.support.mybatisplus.injector.methods.DeleteByMultiId; import com.caspe.base.support.mybatisplus.injector.methods.SelectByMultiId; import com.caspe.base.support.mybatisplus.injector.methods.UpdateByMultiId; import java.util.ArrayList; import java.util.List; public class SqlInjectorX extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = new ArrayList<>(); // Priority injection AutoResultMap generation method methodList.add(new BetterAutoResultMap()); methodList.addAll(super.getMethodList(mapperClass)); return methodList; } }
- Add a custom Sql injector to the container
@Configuration @Import({SqlInjectorX.class}) public class MybatisPlusAutoConfiguration { }
verification
First, according to the optimization idea, the attribute autoResultMap=true must be deleted first.
Question 1 will no longer exist. Question 2 uses our customized ResultMap. The column attribute and property attribute will be consistent, and everything will become simple and natural.