MyBatisPlus的autoResultMap生成策略實現(xiàn)
前言
使用MyBatis-Plus的字段類型處理器,只需一個注解,就可以很方便的將數(shù)組、對象等數(shù)據(jù)直接映射到實體類中。
@Data @Accessors(chain = true) @TableName(autoResultMap = true) public class User { private Long id; ... /** * 注意??! 必須開啟映射注解 * * @TableName(autoResultMap = true) * * 以下兩種類型處理器,二選一 也可以同時存在 * * 注意!!選擇對應(yīng)的 JSON 處理器也必須存在對應(yīng) JSON 解析依賴包 */ @TableField(typeHandler = JacksonTypeHandler.class) // @TableField(typeHandler = FastjsonTypeHandler.class) private OtherInfo otherInfo; }
該注解對應(yīng)了 XML 中寫法為
<result column="other_info" jdbcType="VARCHAR" property="otherInfo" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
實現(xiàn)的原理可以參考TableInfo的源碼initResultMapIfNeed方法:
/** * 自動構(gòu)建 resultMap 并注入(如果條件符合的話) */ 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; } }
存在的問題
當使用autoResultMap=true
時, MP會做以下事情:
- 如果字段的Java類型不是基本類型,則會強制提示你設(shè)置
typeHandler
屬性,比如這么寫是不行的:
/** * IN 查詢 */ public static final String IN = "%s IN <foreach item=\"item\" collection=\"%s\" separator=\",\" open=\"(\" close=\")\" index=\"\">#{item}</foreach>"; @TableField(value="code",select=false, condition=IN ) private Strinng[] codes;
- 如果字段比較復(fù)雜,比如包含函數(shù),帶有表別名限定等,那么自動生成的resultMap對應(yīng)的column屬性則會變得很奇怪:
@TableField(value = "array_agg(distinct code)",jdbcType = JdbcType.VARCHAR, typeHandler = ArrayTypeHandler.class) private String[] codes;
上面的代碼中,生成的resultMap的column屬性是 rray_agg(distinct code , (value屬性前后各截取一位),具體的邏輯可以查看MP的源碼:
TableFieldInfo.java
/** * 獲取 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 這會有影響 registry.register(typeHandler); } builder.typeHandler(typeHandler); } return builder.build(); }
StringUtils.java
/** * 驗證字符串是否是數(shù)據(jù)庫字段 */ private static final Pattern P_IS_COLUMN = Pattern.compile("^\\w\\S*[\\w\\d]*$"); /** * 判斷字符串是否符合數(shù)據(jù)庫字段的命名 * * @param str 字符串 * @return 判斷結(jié)果 */ public static boolean isNotColumnName(String str) { return !P_IS_COLUMN.matcher(str).matches(); } /** * 獲取真正的字段名 * * @param column 字段名 * @return 字段名 */ public static String getTargetColumn(String column) { if (isNotColumnName(column)) { return column.substring(1, column.length() - 1); } return column; }
是的,就是這么簡單粗暴,這里吐槽一下國內(nèi)開源軟件的毛病,邏輯莫名其妙,而且git上對于開發(fā)者提出的疑問視而不見。
優(yōu)化思路
其實如果一個字段存在typeHander
屬性,那就必須要建一個ResultMap來處理類型映射了,根本不需要再畫蛇添足的指定autoResultMap=true
, 不過也好在有這個屬性,MP才會自動生成一個ResultMap,這樣我們就可以在不指定這個屬性的時候,生成自己的ResultMap了。
直接修改官方源代碼不是我的風(fēng)格,好在MP提供了Sql注入器,在往Mapper中注入方法之前,我們把ResultMap生成就可以了。
優(yōu)化步驟
- 創(chuàng)建抽象注入方法的子類:BetterAutoResultMap.java
import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.caspe.base.support.mybatisplus.toolkit.ConstantsX; 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; /** * 優(yōu)化的AutoResultMap生成策略 * 只要字段中指定了TypeHandler即自動生成ResultMap, 而不需要指定autoResultMap=true * 且只對設(shè)置了TypeHandler的字段生成ResultMapping, 而不是所有的字段 * * @author yongfeng_meng */ public class BetterAutoResultMap extends AbstractMethod { /** * 強制重設(shè)TableInfo的resultMap屬性 */ static Field ResultMapOfTableInfo; /** * 強制重設(shè)TableInfo的autoInitResultMap屬性 */ 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(); } } /** * 注入自定義 MappedStatement * <p> * 當實體類沒有指定autoResultMap和resultMap時, 即可使用該方法自動注入ResultMap * * @param mapperClass mapper 接口 * @param modelClass mapper 泛型 * @param tableInfo 數(shù)據(jù)庫表反射信息 * @return MappedStatement */ @SneakyThrows @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { if (!tableInfo.isAutoInitResultMap() && tableInfo.getResultMap() == null) { // 只要字段中指定了TypeHandler即自動生成ResultMap if (tableInfo.getFieldList().stream().filter(this::needToAutoMap).findAny().isPresent()) { // 生成ResultMap ResultMap resultMap = generatorResultMap(tableInfo); configuration.addResultMap(resultMap); // 將ResultMap屬性設(shè)置到TableInfo ResultMapOfTableInfo.set(tableInfo, resultMap.getId()); AutoInitResultMapOfTableInfo.set(tableInfo, true); } } return null; } /** * 構(gòu)建 resultMap */ ResultMap generatorResultMap(TableInfo tableInfo) { String resultMapId = tableInfo.getCurrentNamespace() + DOT + ConstantsX.MYBATIS_PLUS_X + UNDERSCORE + tableInfo.getEntityType().getSimpleName(); List<ResultMapping> resultMappings = tableInfo.getFieldList().stream().filter(this::needToAutoMap) .map(this::getResultMapping).collect(Collectors.toList()); return new ResultMap.Builder(configuration, resultMapId, tableInfo.getEntityType(), resultMappings).build(); } boolean needToAutoMap(TableFieldInfo f) { return f.getTypeHandler() != null && f.getTypeHandler() != UnknownTypeHandler.class; } /** * 構(gòu)建 resultMapping (只針對typeHandler的字段) * * @param tableFieldInfo * @return */ ResultMapping getResultMapping(TableFieldInfo tableFieldInfo) { String column = tableFieldInfo.getColumn(); String property = tableFieldInfo.getProperty(); if (!StringUtils.underlineToCamel(column).equals(property)) { column = property; } ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, 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(); } }
- 創(chuàng)建SQL注入器的自定義類: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<>(); // 優(yōu)先注入AutoResultMap生成方法 methodList.add(new BetterAutoResultMap()); methodList.addAll(super.getMethodList(mapperClass)); return methodList; } }
- 將自定義Sql注入器加入到容器中
@Configuration @Import({SqlInjectorX.class}) public class MybatisPlusAutoConfiguration { }
驗證
首先按照優(yōu)化思路,肯定先要將autoResultMap=true
這個屬性刪掉。
問題1將不復(fù)存在,問題2使用了我們自定義的ResultMap,column屬性和property屬性將一致,一切變得簡單而自然。
到此這篇關(guān)于MyBatisPlus的autoResultMap生成策略實現(xiàn)的文章就介紹到這了,更多相關(guān)MyBatisPlus autoResultMap生成策略內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring的兩種事務(wù)管理機制的基本概念和demo示例
Spring事務(wù)包括聲明式事務(wù)管理和注解式事務(wù)管理,我們通過概念和小demo的形式一步一步地來一起學(xué)習(xí)這個知識點,需要的朋友可以參考下2023-07-07spring帶bean和config如何通過main啟動測試
這篇文章主要介紹了spring帶bean和config,通過main啟動測試,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07解決jackson反序列化失敗InvalidFormatException:Can not dese
這篇文章主要介紹了解決jackson反序列化失敗InvalidFormatException:Can not deserialize value of type java.util.Date問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12JAVA線程池監(jiān)控以及動態(tài)調(diào)整示例詳解
這篇文章主要為大家介紹了JAVA線程池監(jiān)控以及動態(tài)調(diào)整示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09java創(chuàng)建子類對象設(shè)置并調(diào)用父類的變量操作
這篇文章主要介紹了java創(chuàng)建子類對象設(shè)置并調(diào)用父類的變量操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01