mybatis

Mybatis通用枚举 Enum TypeHandler

2021-08-06  本文已影响0人  xin_5457

介绍

Mybatis 内置提供了两种枚举TypeHandler,EnumTypeHandler和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,做了一些小改动)

  1. 定义一个注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface EnumValue {
 
}
  1. 实现通用的枚举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);
        }
    }
}

使用说明

  1. 配置
mybatis:
    configuration:
        default-enum-type-handler: com.example.mybatis.typeHandler.MybatisEnumTypeHandler
  1. 在对应的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);
    }
}

配合框架使用

  1. 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);
}

有两种方式处理:

mapper:
     enum-as-simple-type: true
@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;
 
}
  1. MybatisPlus使用
    mybatis-plus内置了Enum的支持,可以直接配置Enum的目录。
mybatis-plus:
    type-enums-package: com.example.enums
上一篇 下一篇

猜你喜欢

热点阅读