介绍
Mybatis 内置提供了两种枚举TypeHandler,EnumTypeHandler和EnumOrdinalTypeHandler
EnumTypeHandler
默认的枚举TypeHandler,入库的值为枚举的name
EnumOrdinalTypeHandler
入库的值为枚举的位置
但是现实处理的时候,我们Enum定义时可能是需要存储code,上面两种都不符合我们的需求,这时候就需要自定义TypeHandler了。
public enum GenderEnum {
UNKNOWN(0),
MALE(1),
FEMALE(2);
@JsonValue
@Getter
private int code;
GenderEnum(int code) {
this.code = code;
}
private static final Map VALUES = new HashMap<>();
static {
for (final GenderEnum gender : GenderEnum.values()) {
GenderEnum.VALUES.put(gender.getCode(), gender);
}
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public GenderEnum of(int code) {
return VALUES.get(code);
}
}
实现方案
通常可以为每个Enum类配置一个TypeHandler,但是这种比较繁琐,这里通过注解配合Mybatis的默认EnumTypeHander配置实现通用枚举TypeHander。(代码来自Mybatis-Plus,做了一些小改动)
定义一个注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface EnumValue {
}
实现通用的枚举TypeHandler
在mybatis-plus提供的TypeHandler上做了简单修改,会取枚举添加@EnumValue注解的属性值,如果未发现注解,使用枚举的name。
@Slf4j
public class MybatisEnumTypeHandler
private static final Map TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
private final Class enumClassType;
private final Class> propertyType;
private final Invoker getInvoker;
private static final Map, Class>> PRIMITIVE_WRAPPER_TYPE_MAP = new IdentityHashMap<>(8);
private static final Map, Class>> PRIMITIVE_TYPE_TO_WRAPPER_MAP = new IdentityHashMap<>(8);
static {
PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, boolean.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Byte.class, byte.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Character.class, char.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Double.class, double.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Float.class, float.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Integer.class, int.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Long.class, long.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Short.class, short.class);
for (Map.Entry, Class>> entry : PRIMITIVE_WRAPPER_TYPE_MAP.entrySet()) {
PRIMITIVE_TYPE_TO_WRAPPER_MAP.put(entry.getValue(), entry.getKey());
}
}
public MybatisEnumTypeHandler(Class enumClassType) {
if (enumClassType == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.enumClassType = enumClassType;
MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY);
Optional name = findEnumValueFieldName(this.enumClassType);
if (name.isPresent()) {
this.propertyType = resolvePrimitiveIfNecessary(metaClass.getGetterType(name.get()));
this.getInvoker = metaClass.getGetInvoker(name.get());
} else {
log.info(String.format("Could not find @EnumValue in Class: %s.", this.enumClassType.getName()));
this.propertyType = String.class;
this.getInvoker = null;
}
}
/**
* 查找标记标记EnumValue字段
*
* @param clazz class
* @return EnumValue字段
* @since 3.3.1
*/
public static Optional findEnumValueFieldName(Class> clazz) {
if (clazz != null && clazz.isEnum()) {
String className = clazz.getName();
return Optional.ofNullable(TABLE_METHOD_OF_ENUM_TYPES.computeIfAbsent(className, key -> {
Optional fieldOptional = findEnumValueAnnotationField(clazz);
return fieldOptional.map(Field::getName).orElse(null);
}));
}
return Optional.empty();
}
private static Optional findEnumValueAnnotationField(Class> clazz) {
return Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EnumValue.class)).findFirst();
}
private static Class> resolvePrimitiveIfNecessary(Class> clazz) {
return (clazz.isPrimitive() && clazz != void.class ? PRIMITIVE_TYPE_TO_WRAPPER_MAP.get(clazz) : clazz);
}
@SuppressWarnings("Duplicates")
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType)
throws SQLException {
if (jdbcType == null) {
ps.setObject(i, this.getValue(parameter));
} else {
// see r3589
ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
Object value = rs.getObject(columnName, this.propertyType);
if (null == value && rs.wasNull()) {
return null;
}
return this.valueOf(value);
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Object value = rs.getObject(columnIndex, this.propertyType);
if (null == value && rs.wasNull()) {
return null;
}
return this.valueOf(value);
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Object value = cs.getObject(columnIndex, this.propertyType);
if (null == value && cs.wasNull()) {
return null;
}
return this.valueOf(value);
}
private E valueOf(Object value) {
E[] es = this.enumClassType.getEnumConstants();
return Arrays.stream(es).filter((e) -> equalsValue(value, getValue(e))).findAny().orElse(null);
}
/**
* 值比较
*
* @param sourceValue 数据库字段值
* @param targetValue 当前枚举属性值
* @return 是否匹配
* @since 3.3.0
*/
protected boolean equalsValue(Object sourceValue, Object targetValue) {
String sValue = String.valueOf(sourceValue).trim();
String tValue = String.valueOf(targetValue).trim();
if (sourceValue instanceof Number && targetValue instanceof Number
&& new BigDecimal(sValue).compareTo(new BigDecimal(tValue)) == 0) {
return true;
}
return Objects.equals(sValue, tValue);
}
/**
* 取值,如果有@EnumValue注解,会取该属性的值,否则取枚举的name
*/
private Object getValue(E object) {
if (this.getInvoker == null) {
return object.name();
}
try {
return this.getInvoker.invoke(object, new Object[0]);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
使用说明
配置
mybatis:
configuration:
default-enum-type-handler: com.example.mybatis.typeHandler.MybatisEnumTypeHandler
在对应的Enum属性上添加@EnumValue注解
public enum GenderEnum {
UNKNOWN(0),
MALE(1),
FEMALE(2);
@JsonValue
@EnumValue
@Getter
private int code;
GenderEnum(int code) {
this.code = code;
}
private static final Map VALUES = new HashMap<>();
static {
for (final GenderEnum gender : GenderEnum.values()) {
GenderEnum.VALUES.put(gender.getCode(), gender);
}
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public GenderEnum of(int code) {
return VALUES.get(code);
}
}
配合框架使用
tk通用Mapper使用
tk mapper会默认忽略Enum的属性字段。
相关源码:
for (EntityField field : fields) {
//如果启用了简单类型,就做简单类型校验,如果不是简单类型,直接跳过
//3.5.0 如果启用了枚举作为简单类型,就不会自动忽略枚举类型
//4.0 如果标记了 Column 或 ColumnType 注解,也不忽略
if (config.isUseSimpleType()
&& !field.isAnnotationPresent(Column.class)
&& !field.isAnnotationPresent(ColumnType.class)
&& !(SimpleTypeUtil.isSimpleType(field.getJavaType())
||
(config.isEnumAsSimpleType() && Enum.class.isAssignableFrom(field.getJavaType())))) {
continue;
}
processField(entityTable, field, config, style);
}
有两种方式处理:
第一种:3.5.0 以上可以配置枚举处理
mapper:
enum-as-simple-type: true
第二种:4.0以上对应属性上添加@Column或者@ColumnType注解
@Data
@Table(name = “user”)
public class User implements Serializable {
private static final long serialVersionUID = -53635849389000664L;
/**
* 主键ID
/
@Id
private Long id;
/*
* 姓名
/
private String name;
/*
* 年龄
/
private Integer age;
/*
* 邮箱
*/
private String email;
/**
* 性别
*/
@Column
private GenderEnum gender;
}
MybatisPlus使用
mybatis-plus内置了Enum的支持,可以直接配置Enum的目录。
mybatis-plus:
type-enums-package: com.example.enums