使用lombok注解導(dǎo)致mybatis-plus TypeHandler失效的解決
問(wèn)題描述
建立實(shí)體其中一個(gè)字段為枚舉類(lèi)
/** * @author liuxishan 2023/6/2 */ @Data @Builder @TableName(value = "hot_event",autoResultMap = false) @EqualsAndHashCode(callSuper = true) public class HotEvent extends BaseAuditor { @TableId(type = IdType.ASSIGN_ID) private String id; ············ ············ /** * 創(chuàng)建方式 */ @TableField(typeHandler = HotEventCreationMethodHandler.class,jdbcType = JdbcType.INTEGER) private CreationMethodEnum creationMethod; }
public enum CreationMethodEnum { SYSTEM(0, "系統(tǒng)導(dǎo)入"), MANUAL_CREATE(1, "手動(dòng)導(dǎo)入"); /** * 用于排序 */ private Integer value; private String msg; CreationMethodEnum(Integer value, String msg) { this.value = value; this.msg = msg; } public Integer getValue() { return value; } public String getMsg() { return msg; } public static CreationMethodEnum valueOf(int value) { for (CreationMethodEnum creationMethodEnum : CreationMethodEnum.values()) { if (creationMethodEnum.value.equals(value)) { return creationMethodEnum; } } return null; } }
希望數(shù)據(jù)庫(kù)存的時(shí)對(duì)應(yīng)的數(shù)字
為了和數(shù)據(jù)庫(kù)進(jìn)行轉(zhuǎn)換,使用了typeHandler
public class HotEventCreationMethodHandler implements TypeHandler<CreationMethodEnum> { @Override public void setParameter(PreparedStatement preparedStatement, int i, CreationMethodEnum creationMethodEnum, JdbcType jdbcType) throws SQLException { preparedStatement.setInt(i,creationMethodEnum.getValue()); } @Override public CreationMethodEnum getResult(ResultSet resultSet, String columnName) throws SQLException { int anInt = resultSet.getInt(columnName); return CreationMethodEnum.valueOf(anInt); } @Override public CreationMethodEnum getResult(ResultSet resultSet, int i) throws SQLException { int anInt = resultSet.getInt(i); return CreationMethodEnum.valueOf(anInt); } @Override public CreationMethodEnum getResult(CallableStatement callableStatement, int i) throws SQLException { int anInt = callableStatement.getInt(i); return CreationMethodEnum.valueOf(anInt); } }
測(cè)試發(fā)現(xiàn)
@Test public void pageContents() { HotEvent hotEvent = HotEvent.builder() .eventName("測(cè)試事件名稱(chēng)") .eventOverview("測(cè)試測(cè)試") .eventType(EventTypeEnum.FUND) .bizId("1245454") .creationMethod(CreationMethodEnum.MANUAL_CREATE) .publishingTime(LocalDateTime.now()) .url("http://baidu.com") .publishingAgency("機(jī)構(gòu)") .build(); hotEventDao.insert(hotEvent); List<HotEvent> hotEvents = hotEventDao.selectList(Wrappers.<HotEvent>lambdaQuery().eq(HotEvent::getCreationMethod, 1)); }
插入/更新typeHandler生效,但是查詢(xún)時(shí)在將數(shù)據(jù)庫(kù)數(shù)據(jù)映射成java實(shí)體類(lèi)的時(shí)候報(bào)錯(cuò)
Caused by: java.lang.IllegalArgumentException: No enum constant com.yiyouliao.rivers.content.api.enums.CreationMethodEnum.1
at java.lang.Enum.valueOf(Enum.java:238)
at org.apache.ibatis.type.EnumTypeHandler.getNullableResult(EnumTypeHandler.java:49)
at org.apache.ibatis.type.EnumTypeHandler.getNullableResult(EnumTypeHandler.java:26)
at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:85)
... 67 more
設(shè)置的HotEventCreationMethodHandler并沒(méi)有生效
我們知道,在不開(kāi)啟autoResultMap時(shí),會(huì)導(dǎo)致TableField對(duì)于查詢(xún)返回的結(jié)果不生效。(對(duì)于更新無(wú)影響)
因此首先檢查在實(shí)體類(lèi)注解上已經(jīng)開(kāi)啟了autoResultMap,仍然是報(bào)錯(cuò)(mybatisPlus會(huì)根據(jù)我們的實(shí)體類(lèi)的類(lèi)型,為我們自動(dòng)注入resultMap,注入的resultMap也會(huì)自動(dòng)推斷出需要使用的類(lèi)型轉(zhuǎn)換器當(dāng)然我們也可以指定)
@TableName(value = "hot_event",autoResultMap = true)
問(wèn)題排查
網(wǎng)上查了typeHandler失效的原因,大部分都說(shuō)的是檢查autoResultMap。無(wú)奈只能跟源碼進(jìn)行檢查
問(wèn)題出在查詢(xún)結(jié)果后將結(jié)果轉(zhuǎn)換為java實(shí)體類(lèi)的地方,由DefaultResultSetHandler進(jìn)行處理
關(guān)鍵方法
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.lang.String)
在處理一行數(shù)據(jù)是,正常情況是首先創(chuàng)建java實(shí)體對(duì)象,然后根據(jù)resultMap的字段映射 進(jìn)行轉(zhuǎn)換
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); //創(chuàng)建java實(shí)體對(duì)象 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; }
但是發(fā)現(xiàn)我們?cè)赾reateResultObject這一步就報(bào)錯(cuò)了,
Caused by: java.lang.IllegalArgumentException: No enum constant com.yiyouliao.rivers.content.api.enums.CreationMethodEnum.1
跟進(jìn)去原因就找到了,在創(chuàng)建對(duì)象時(shí)
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.util.List<java.lang.Class<?>>, java.util.List<java.lang.Object>, java.lang.String)
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException { final Class<?> resultType = resultMap.getType(); final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory); final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings(); if (hasTypeHandlerForResultObject(rsw, resultType)) { return createPrimitiveResultObject(rsw, resultMap, columnPrefix); } else if (!constructorMappings.isEmpty()) { return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix); } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { return objectFactory.create(resultType); } else if (shouldApplyAutomaticMappings(resultMap, false)) { return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs); } throw new ExecutorException("Do not know how to create an instance of " + resultType); }
這里有幾種情況。
- 有對(duì)應(yīng)typeHandler處理當(dāng)前類(lèi)型,由typeHandler根據(jù)resultSet創(chuàng)建
- 有constructorMappings(構(gòu)造映射)我們沒(méi)有設(shè)置
- 有默認(rèn)無(wú)參構(gòu)造,創(chuàng)建一個(gè)空對(duì)象,使用resultMap進(jìn)行映射
- 無(wú)默認(rèn)構(gòu)造,那么根據(jù)構(gòu)造函數(shù),自動(dòng)映射(不使用resultMap)進(jìn)行映射
發(fā)現(xiàn)進(jìn)了第四個(gè)處理邏輯導(dǎo)致報(bào)錯(cuò),那么問(wèn)題就發(fā)現(xiàn)了,是因?yàn)槲覀兊膶?shí)體類(lèi)沒(méi)有默認(rèn)的無(wú)參構(gòu)造函數(shù)
問(wèn)題結(jié)論
回頭檢查實(shí)體類(lèi)發(fā)現(xiàn)我們加了@Builder注解,會(huì)為實(shí)體類(lèi)創(chuàng)建構(gòu)造函數(shù),但是也導(dǎo)致實(shí)體類(lèi)沒(méi)有了無(wú)參構(gòu)造。
因此加上注解NoArgsConstructor,問(wèn)題解決
/** * @author liuxishan 2023/6/2 */ @Data @Builder @TableName(value = "hot_event",autoResultMap = false) @EqualsAndHashCode(callSuper = true) @AllArgsConstructor @NoArgsConstructor public class HotEvent extends BaseAuditor { @TableId(type = IdType.ASSIGN_ID) private String id; ············ ············ /** * 創(chuàng)建方式 */ @TableField(typeHandler = HotEventCreationMethodHandler.class,jdbcType = JdbcType.INTEGER) private CreationMethodEnum creationMethod; }
Tips
在查詢(xún)時(shí)使用Wrappers構(gòu)造查詢(xún)條件時(shí),查詢(xún)字段是枚舉時(shí),不能直接使用枚舉,需要使用數(shù)據(jù)庫(kù)對(duì)應(yīng)的值進(jìn)行查詢(xún),因?yàn)閠ypeHandler僅僅處理的是實(shí)體類(lèi)字段和jdbcType的轉(zhuǎn)換。
- 錯(cuò)誤寫(xiě)法
List<HotEvent> hotEvents = hotEventDao.selectList( Wrappers.<HotEvent>lambdaQuery().eq(HotEvent::getCreationMethod, CreationMethodEnum.MANUAL_CREATE) );
- 正確寫(xiě)法
List<HotEvent> hotEvents = hotEventDao.selectList( Wrappers.<HotEvent>lambdaQuery().eq(HotEvent::getCreationMethod,1) );
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Eclipse+Java+Swing實(shí)現(xiàn)斗地主游戲(代碼)
這篇文章主要介紹了Eclipse+Java+Swing實(shí)現(xiàn)斗地主游戲并附上詳細(xì)的代碼實(shí)現(xiàn),正在學(xué)習(xí)的你可以當(dāng)小練習(xí)練練,希望對(duì)你有所幫助2022-01-01java Runnable接口創(chuàng)建線(xiàn)程
這篇文章主要介紹了java Runnable接口創(chuàng)建線(xiàn)程的相關(guān)資料,需要的朋友可以參考下2017-07-07Spring Boot 配置MySQL數(shù)據(jù)庫(kù)重連的操作方法
這篇文章主要介紹了Spring Boot 配置MySQL數(shù)據(jù)庫(kù)重連的操作方法,需要的朋友可以參考下2018-04-04java httpclient設(shè)置超時(shí)時(shí)間和代理的方法
這篇文章主要介紹了java httpclient設(shè)置超時(shí)時(shí)間和代理的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02Java基本數(shù)據(jù)類(lèi)型包裝類(lèi)原理解析
這篇文章主要介紹了Java基本數(shù)據(jù)類(lèi)型包裝類(lèi)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05簡(jiǎn)單了解JAVA public class與class區(qū)別
這篇文章主要介紹了簡(jiǎn)單了解JAVA public class與class區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03