java.lang.IllegalArgumentException: No enum constant XXX at java.base/java.lang.Enum.valueOf(Enum.java:273)
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:86)
at com.baomidou.mybatisplus.core.handlers.CompositeEnumTypeHandler.getResult(CompositeEnumTypeHandler.java:62)
at com.baomidou.mybatisplus.core.handlers.CompositeEnumTypeHandler.getResult(CompositeEnumTypeHandler.java:37)
at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.applyAutomaticMappings(DefaultResultSetHandler.java
mybatis 如何处理枚举?有一个自带处理oridal 就是按位置来 这个枚举声明容易修改位置
org.apache.ibatis.type.EnumOrdinalTypeHandler<E>
:该类实现了枚举类型和Integer类型的相互转换。
但是给转换仅仅是将对应的枚举转换为其索引位置,也就是"ordinal()"方法获取到的值。对应自定义的int值,该类无能为力。
mybatis-plus 如何处理枚举?他是要写他自定义的注解 @EnumValue 或者实现接口IEnum接口
@Getter
public enum GenderEnum{
WOMAN(0,"女"),
MAN(1, "男");
@EnumValue
private final Integer code;
@JsonValue
private final String desc;
GenderEnum(Integer code,String desc){
this.code = code;
this.desc = desc;
}
}
import com.baomidou.mybatisplus.annotation.IEnum;
/**
* 性别枚举
*/
public enum GenderEnum implements IEnum<Integer> {
UNKNOWN(0, "未知"),
MALE(1, "男"),
FEMALE(2, "女");
@JsonValue // 序列化枚举值为 接口出参;接口入参(RequestBody),反序列化为枚举值
private final Integer value;
private final String description;
@Override
public Integer getValue() {
return value; // 标记数据库存的值是 value
}
}
很明显我们一般只关心序列化的注解,那么又增加一套注解麻烦
平时也就使用jackson 注解 或者gson 注解
这里我给出一个全自动处理的方案
package com.nbxxf.kpower.spring.mybatis.typeHandler;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.annotations.SerializedName;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.security.PrivilegedAction;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 全局自动化处理枚举
* 业务只需要
* 对于jackson 只需要在枚举常量添加@JsonProperty注解
* 对于gson 只需要在枚举常量添加需要@SerializedName
*
* @version v1.0
* @authpr youxuan
* @date 2024-03-26 10:16
*/
@MappedTypes(Enum.class)
public class AutoEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
@SuppressWarnings("ALL")
private static class EnumTypeAdapter<T extends Enum<T>> {
final Map<String, T> nameToConstant = new HashMap<>();
final Map<T, String> constantToName = new HashMap<>();
public Field[] parse(final Class<T> classOfT) {
try {
Field[] constantFields = java.security.AccessController.doPrivileged(new PrivilegedAction<Field[]>() {
@Override
public Field[] run() {
Field[] fields = classOfT.getDeclaredFields();
ArrayList<Field> constantFieldsList = new ArrayList<>(fields.length);
for (Field f : fields) {
if (f.isEnumConstant()) {
constantFieldsList.add(f);
}
}
Field[] constantFields = constantFieldsList.toArray(new Field[0]);
AccessibleObject.setAccessible(constantFields, true);
return constantFields;
}
});
return constantFields;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Field[] fields = classOfT.getDeclaredFields();
ArrayList<Field> constantFieldsList = new ArrayList<>(fields.length);
for (Field f : fields) {
if (f.isEnumConstant()) {
constantFieldsList.add(f);
}
}
Field[] constantFields = constantFieldsList.toArray(new Field[0]);
AccessibleObject.setAccessible(constantFields, true);
return constantFields;
}
public EnumTypeAdapter(final Class<T> classOfT) {
try {
// Uses reflection to find enum constants to work around name mismatches for obfuscated classes
// Reflection access might throw SecurityException, therefore run this in privileged context;
// should be acceptable because this only retrieves enum constants, but does not expose anything else
Field[] constantFields = parse(classOfT);
for (Field constantField : constantFields) {
@SuppressWarnings("unchecked")
T constant = (T) (constantField.get(null));
String name = constant.name();
String toStringVal = constant.toString();
//jackson 方式
JsonProperty jsonProperty = constantField.getAnnotation(JsonProperty.class);
if (jsonProperty != null) {
name = jsonProperty.value();
}
JsonAlias jsonAlias = constantField.getAnnotation(JsonAlias.class);
if (jsonAlias != null) {
for (String alternate : jsonAlias.value()) {
nameToConstant.put(alternate, constant);
}
}
//gson
SerializedName annotation = constantField.getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
for (String alternate : annotation.alternate()) {
nameToConstant.put(alternate, constant);
}
}
nameToConstant.put(name, constant);
nameToConstant.put(toStringVal, constant);
constantToName.put(constant, name);
}
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
public T read(Object in) throws IOException {
String key = in.toString();
T constant = nameToConstant.get(key);
return constant;
}
public T getValue(T in) throws IOException {
String key = in.toString();
T constant = nameToConstant.get(key);
return constant;
}
}
EnumTypeAdapter<E> enumTypeAdapter;
public AutoEnumTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
enumTypeAdapter = new EnumTypeAdapter(type);
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType)
throws SQLException {
if (jdbcType == null)
ps.setString(i, enumTypeAdapter.constantToName.getOrDefault(parameter, parameter.name()));
else
ps.setObject(i, enumTypeAdapter.constantToName.getOrDefault(parameter, parameter.name()), jdbcType.TYPE_CODE);
}
@Override
public E getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return enumTypeAdapter.nameToConstant.get(rs.getString(columnName));
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return enumTypeAdapter.nameToConstant.get(rs.getString(columnIndex));
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return enumTypeAdapter.nameToConstant.get(cs.getString(columnIndex));
}
}
然后在application.properties 注册一下
mybatis-plus.configuration.default-enum-type-handler=com.nbxxf.kpower.spring.mybatis.typeHandler.AutoEnumTypeHandler 当然也可以通过代码来注册(找一个初始化bean的组件来调用下面的代码)
package com.nbxxf.kpower.spring.mybatis;
import com.baomidou.mybatisplus.core.handlers.CompositeEnumTypeHandler;
import com.nbxxf.kpower.spring.mybatis.typeHandler.AutoEnumTypeHandler;
import org.apache.ibatis.type.TypeHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
public class MyBatisHelper {
public static void setDefaultEnumTypeHandlerIfAbsent(ApplicationContext context) {
if (!hasDefaultEnumTypeHandler(context)) {
CompositeEnumTypeHandler.setDefaultEnumTypeHandler(AutoEnumTypeHandler.class);
}
}
public static void setDefaultEnumTypeHandlerIfAbsent(ApplicationContext context, Class<? extends TypeHandler> defaultEnumTypeHandler) {
if (!hasDefaultEnumTypeHandler(context)) {
CompositeEnumTypeHandler.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
}
}
public static boolean hasDefaultEnumTypeHandler(ApplicationContext context) {
Environment environment = context.getEnvironment();
String defaultEnumTypeHandler = environment.getProperty("mybatis-plus.configuration.default-enum-type-handler");
if (!StringUtils.hasLength(defaultEnumTypeHandler)) {
defaultEnumTypeHandler = environment.getProperty("mybatis.configuration.default-enum-type-handler");
}
return StringUtils.hasLength(defaultEnumTypeHandler);
}
}
这样我们写枚举 就不用关心其他第三方注解
* 全局自动化处理枚举 * 业务只需要 * 对于jackson 只需要在枚举常量添加@JsonProperty注解 * 对于gson 只需要在枚举常量添加需要@SerializedName 或者都不写序列化注解,那么会自动序列化枚举声明的字段名字 而不会导致报错 且两者jackson和gson共存,如果没有引入gson 那么将gson对应的报错删除就行