Mybatis通用枚举 Enum TypeHandler
介绍
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<Integer, GenderEnum> 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<E extends Enum<E>> extends BaseTypeHandler<E> { private static final Map<String, String> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>(); private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory(); private final Class<E> enumClassType; private final Class<?> propertyType; private final Invoker getInvoker; private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPER_TYPE_MAP = new IdentityHashMap<>(8); private static final Map<Class<?>, 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<?>, Class<?>> entry : PRIMITIVE_WRAPPER_TYPE_MAP.entrySet()) { PRIMITIVE_TYPE_TO_WRAPPER_MAP.put(entry.getValue(), entry.getKey()); } } public MybatisEnumTypeHandler(Class<E> enumClassType) { if (enumClassType == null) { throw new IllegalArgumentException("Type argument cannot be null"); } this.enumClassType = enumClassType; MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY); Optional<String> 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<String> findEnumValueFieldName(Class<?> clazz) { if (clazz != null && clazz.isEnum()) { String className = clazz.getName(); return Optional.ofNullable(TABLE_METHOD_OF_ENUM_TYPES.computeIfAbsent(className, key -> { Optional<Field> fieldOptional = findEnumValueAnnotationField(clazz); return fieldOptional.map(Field::getName).orElse(null); })); } return Optional.empty(); } private static Optional<Field> 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<Integer, GenderEnum> 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); } }
MybatisPlus使用
mybatis-plus内置了Enum的支持,可以直接配置Enum的目录
mybatis-plus: type-enums-package: com.example.enums
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!