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 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 extends BaseTypeHandler {

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

你可能感兴趣的:(mybatis,java,开发语言)