MyBatisPlus的autoResultMap生成策略實(shí)現(xiàn)
前言
使用MyBatis-Plus的字段類型處理器,只需一個(gè)注解,就可以很方便的將數(shù)組、對(duì)象等數(shù)據(jù)直接映射到實(shí)體類中。
@Data
@Accessors(chain = true)
@TableName(autoResultMap = true)
public class User {
private Long id;
...
/**
* 注意!! 必須開(kāi)啟映射注解
*
* @TableName(autoResultMap = true)
*
* 以下兩種類型處理器,二選一 也可以同時(shí)存在
*
* 注意??!選擇對(duì)應(yīng)的 JSON 處理器也必須存在對(duì)應(yīng) JSON 解析依賴包
*/
@TableField(typeHandler = JacksonTypeHandler.class)
// @TableField(typeHandler = FastjsonTypeHandler.class)
private OtherInfo otherInfo;
}
該注解對(duì)應(yīng)了 XML 中寫法為
<result column="other_info" jdbcType="VARCHAR" property="otherInfo" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
實(shí)現(xiàn)的原理可以參考TableInfo的源碼initResultMapIfNeed方法:
/**
* 自動(dòng)構(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;
}
}
存在的問(wèn)題
當(dāng)使用autoResultMap=true時(shí), MP會(huì)做以下事情:
- 如果字段的Java類型不是基本類型,則會(huì)強(qiáng)制提示你設(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ù),帶有表別名限定等,那么自動(dòng)生成的resultMap對(duì)應(yīng)的column屬性則會(huì)變得很奇怪:
@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 這會(huì)有影響 registry.register(typeHandler);
}
builder.typeHandler(typeHandler);
}
return builder.build();
}
StringUtils.java
/**
* 驗(yàn)證字符串是否是數(shù)據(jù)庫(kù)字段
*/
private static final Pattern P_IS_COLUMN = Pattern.compile("^\\w\\S*[\\w\\d]*$");
/**
* 判斷字符串是否符合數(shù)據(jù)庫(kù)字段的命名
*
* @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;
}是的,就是這么簡(jiǎn)單粗暴,這里吐槽一下國(guó)內(nèi)開(kāi)源軟件的毛病,邏輯莫名其妙,而且git上對(duì)于開(kāi)發(fā)者提出的疑問(wèn)視而不見(jiàn)。
優(yōu)化思路
其實(shí)如果一個(gè)字段存在typeHander屬性,那就必須要建一個(gè)ResultMap來(lái)處理類型映射了,根本不需要再畫蛇添足的指定autoResultMap=true, 不過(guò)也好在有這個(gè)屬性,MP才會(huì)自動(dòng)生成一個(gè)ResultMap,這樣我們就可以在不指定這個(gè)屬性的時(shí)候,生成自己的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即自動(dòng)生成ResultMap, 而不需要指定autoResultMap=true
* 且只對(duì)設(shè)置了TypeHandler的字段生成ResultMapping, 而不是所有的字段
*
* @author yongfeng_meng
*/
public class BetterAutoResultMap extends AbstractMethod {
/**
* 強(qiáng)制重設(shè)TableInfo的resultMap屬性
*/
static Field ResultMapOfTableInfo;
/**
* 強(qiáng)制重設(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>
* 當(dāng)實(shí)體類沒(méi)有指定autoResultMap和resultMap時(shí), 即可使用該方法自動(dòng)注入ResultMap
*
* @param mapperClass mapper 接口
* @param modelClass mapper 泛型
* @param tableInfo 數(shù)據(jù)庫(kù)表反射信息
* @return MappedStatement
*/
@SneakyThrows
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
if (!tableInfo.isAutoInitResultMap() && tableInfo.getResultMap() == null) {
// 只要字段中指定了TypeHandler即自動(dòng)生成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 (只針對(duì)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àn)證
首先按照優(yōu)化思路,肯定先要將autoResultMap=true這個(gè)屬性刪掉。
問(wèn)題1將不復(fù)存在,問(wèn)題2使用了我們自定義的ResultMap,column屬性和property屬性將一致,一切變得簡(jiǎn)單而自然。
到此這篇關(guān)于MyBatisPlus的autoResultMap生成策略實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)MyBatisPlus autoResultMap生成策略內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
搭建Spring cloud 非阻塞式微服務(wù)架構(gòu)的實(shí)現(xiàn)
本文介紹搭建非阻塞式微服務(wù)架構(gòu),采用Spring Gateway、Spring Security、JWT與Redis,集成EurekaServer、Auth-service、User-service等服務(wù),實(shí)現(xiàn)用戶認(rèn)證、日志跟蹤及統(tǒng)一入口管理,感興趣的可以了解一下2025-08-08
SpringBoot中使用定時(shí)任務(wù)schedule詳解
文章介紹了如何在Spring應(yīng)用中使用@EnableScheduling注解啟用定時(shí)任務(wù),并創(chuàng)建一個(gè)配置類來(lái)定義定時(shí)任務(wù)的執(zhí)行,文章還詳細(xì)解釋了Cron表達(dá)式的基本語(yǔ)法,并提供了多個(gè)常用的Cron表達(dá)式示例,以幫助讀者理解和應(yīng)用定時(shí)任務(wù)2025-11-11
NoHttpResponseException問(wèn)題分析解決記錄
這篇文章主要為大家介紹了NoHttpResponseException問(wèn)題分析解決記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
RestTemplate使用不當(dāng)引發(fā)的問(wèn)題及解決
這篇文章主要介紹了RestTemplate使用不當(dāng)引發(fā)的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
JAVA多線程實(shí)現(xiàn)生產(chǎn)者消費(fèi)者的實(shí)例詳解
這篇文章主要介紹了JAVA多線程實(shí)現(xiàn)生產(chǎn)者消費(fèi)者的實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06
java集合Collection實(shí)現(xiàn)類解析ArrayList?LinkedList及Vector
這篇文章主要為大家介紹了java集合Collection實(shí)現(xiàn)類解析ArrayList?LinkedList及Vector,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-03-03
java設(shè)計(jì)模式之裝飾器模式(Decorator)
這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)模式之裝飾器模式Decorator,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01

