问题描述
建立实体其中一个字段为枚举类
/**
* @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;
············
············
/**
* 创建方式
*/
@TableField(typeHandler = HotEventCreationMethodHandler.class,jdbcType = JdbcType.INTEGER)
private CreationMethodEnum creationMethod;
}
public enum CreationMethodEnum {
SYSTEM(0, "系统导入"),
MANUAL_CREATE(1, "手动导入");
/**
* 用于排序
*/
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;
}
}
希望数据库存的时对应的数字
为了和数据库进行转换,使用了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);
}
}
测试发现
@Test
public void pageContents() {
HotEvent hotEvent = HotEvent.builder()
.eventName("测试事件名称")
.eventOverview("测试测试")
.eventType(EventTypeEnum.FUND)
.bizId("1245454")
.creationMethod(CreationMethodEnum.MANUAL_CREATE)
.publishingTime(LocalDateTime.now())
.url("http://baidu.com")
.publishingAgency("机构")
.build();
hotEventDao.insert(hotEvent);
List<HotEvent> hotEvents = hotEventDao.selectList(Wrappers.<HotEvent>lambdaQuery().eq(HotEvent::getCreationMethod, 1));
}
插入/更新typeHandler生效,但是查询时在将数据库数据映射成java实体类的时候报错
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
设置的HotEventCreationMethodHandler并没有生效
我们知道,在不开启autoResultMap时,会导致TableField对于查询返回的结果不生效。(对于更新无影响)
因此首先检查在实体类注解上已经开启了autoResultMap,仍然是报错(mybatisPlus会根据我们的实体类的类型,为我们自动注入resultMap,注入的resultMap也会自动推断出需要使用的类型转换器当然我们也可以指定)
@TableName(value = "hot_event",autoResultMap = true)
问题排查
网上查了typeHandler失效的原因,大部分都说的是检查autoResultMap。无奈只能跟源码进行检查
问题出在查询结果后将结果转换为java实体类的地方,由DefaultResultSetHandler进行处理
关键方法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)
在处理一行数据是,正常情况是首先创建java实体对象,然后根据resultMap的字段映射进行转换
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//创建java实体对象
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;
}
但是发现我们在createResultObject这一步就报错了,
Caused by: java.lang.IllegalArgumentException: No enum constant com.yiyouliao.rivers.content.api.enums.CreationMethodEnum.1
跟进去原因就找到了,在创建对象时
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);
}
这里有几种情况。
- 有对应typeHandler处理当前类型,由typeHandler根据resultSet创建
- 有constructorMappings(构造映射)我们没有设置
- 有默认无参构造,创建一个空对象,使用resultMap进行映射
- 无默认构造,那么根据构造函数,自动映射(不使用resultMap)进行映射
发现进了第四个处理逻辑导致报错,那么问题就发现了,是因为我们的实体类没有默认的无参构造函数
问题结论
回头检查实体类发现我们加了@Builder注解,会为实体类创建构造函数,但是也导致实体类没有了无参构造。
因此加上注解NoArgsConstructor,问题解决
/**
* @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;
············
············
/**
* 创建方式
*/
@TableField(typeHandler = HotEventCreationMethodHandler.class,jdbcType = JdbcType.INTEGER)
private CreationMethodEnum creationMethod;
}
Tips
在查询时使用Wrappers构造查询条件时,查询字段是枚举时,不能直接使用枚举,需要使用数据库对应的值进行查询,因为typeHandler仅仅处理的是实体类字段和jdbcType的转换。
错误写法文章来源:https://www.toymoban.com/news/detail-469474.html
List<HotEvent> hotEvents = hotEventDao.selectList(
Wrappers.<HotEvent>lambdaQuery().eq(HotEvent::getCreationMethod, CreationMethodEnum.MANUAL_CREATE)
);
正确写法文章来源地址https://www.toymoban.com/news/detail-469474.html
List<HotEvent> hotEvents = hotEventDao.selectList(
Wrappers.<HotEvent>lambdaQuery().eq(HotEvent::getCreationMethod,1)
);
到了这里,关于使用lombok注解导致mybatis-plus TypeHandler失效的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!