介紹
Mybatis 內置提供了兩種枚舉TypeHandler,EnumTypeHandler和EnumOrdinalTypeHandler
EnumTypeHandler
默認的枚舉TypeHandler,入庫的值為枚舉的nameEnumOrdinalTypeHandler
入庫的值為枚舉的位置
但是現實處理的時候,我們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);
}
}
配合框架使用
- 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