在写JPA
实体类的时候有些字段很需要枚举这样的类型来存储,比如说:
public enum GenderType implements ValueEnum<Integer> {
MALE(1, "男"),
FEMALE(2, "女"),
UNKNOWN(0, "未设置");
...
}
JPA
中默认处理枚举的方式是按枚举的顺序保存数字,比如说上面的MALE
保存起来就是0
可以在使用该枚举的字段上标明注解告诉JPA
要如何处理这个枚举类型@Enumerated(EnumType.ORDINAL)
这个可以使用两个值
-
EnumType.ORDINAL
:按枚举的顺序保存数字 -
EnumType.STRING
:保存枚举的值,也就是toString()
的值
上面两个值虽然可以实现枚举的存储,但是是会有缺点的:
-
EnumType.ORDINAL
缺点就是按照枚举的顺序:假如有一天代码中的枚举顺序需要改变,那数据库中的数据就和这些枚举对应不上了;
再比如说数据库中之前这一列数据有使用
-1
这样的值,那么就无法实现了;还有有些枚举需要特定的数字来表示,不是顺序的数字
-
EnumType.STRING
这个的缺点也很明显,就是因为用了String
首先用了
String
以后,枚举就不能改名了用
String
就意味着数据库中的该列必须是字符串类型,不能是数字类型了
如何解决上面的问题?
在JPA
中可以自定义属性转换,我们的枚举就是一个属性,所以我们只要为枚举专门自定义一个属性转换就OK了。
例如下面这样,实现了JPA
的AttributeConverter
接口
@Converter(autoApply = true)
public class EnumConverter implements AttributeConverter<GenderType, Integer> {
@Override
public Integer convertToDatabaseColumn(GenderType attribute) {
return attribute == null ? null : attribute.getValue();
}
@Override
public GenderType convertToEntityAttribute(Integer dbData) {
return dbData == null ? null : GenderType.valueToEnum(dbData);
}
}
GenderType.valueToEnum(dbData)
这个方法是用户自己写的,目的是把查到的值和枚举对应起来
如果不想使用上面的@Converter(autoApply = true)
注解,还有一种方式可以替代:
@Data
@Accessors(chain = true)
@Entity
@Table(name = "user")
public class User {
...
@Convert(converter = EnumConverter.class)
private GenderType gender;
}
这个时候还会想到一件事情,总不能为每个泛型都定义一个这样的转换器吧?
解决这个问题也很简单,将转换器中的类型都使用泛型,并且写成抽象类作为父类,只需要让每个枚举转换器类继承这个类,把泛型填入就可以了。
代码如下:
为所有的枚举类定义一个接口:
public interface BaseEnum<VALUE> {
VALUE getValue();
/**
* 转换值到对应的枚举值
*/
static <ENUM extends BaseEnum<VALUE>, VALUE> ENUM valueToEnum(Class<ENUM> enumType, VALUE value) {
return Stream.of(enumType.getEnumConstants())
.filter(item -> item.getValue().equals(value))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("无法找到枚举值: " + value));
}
}
让每一个枚举类实现该接口:
public enum GenderType implements BaseEnum<Integer> {
MALE(1, "男"),
FEMALE(2, "女"),
UNKNOWN(3, "未设置");
private int value;
private String gender;
GenderType(int value, String gender) {
this.value = value;
}
@Override
public Integer getValue() {
return value;
}
}
转换器直接使用泛型即可:
public abstract class EnumConverter<ENUM extends BaseEnum<VALUE>, VALUE> implements AttributeConverter<ENUM, VALUE> {
private Class<ENUM> enumType;
public EnumConverter(Class<ENUM> enumType) {
this.enumType = enumType;
}
@Override
public VALUE convertToDatabaseColumn(ENUM attribute) {
return attribute == null ? null : attribute.getValue();
}
@Override
public ENUM convertToEntityAttribute(VALUE dbData) {
return dbData == null ? null : BaseEnum.valueToEnum(enumType, dbData);
}
}
上面之所以要写成ENUM extends BaseEnum<VALUE>
这样的形式,是因为要使用BaseEnum
类中的getValue()
方法,上面还要定义一个构造方法,让子类将枚举的类型传过来。
写一个转换器的实现:
@Converter(autoApply = true)
public class GenderTypeConverter extends EnumConverter<GenderType, Integer>{
public GenderTypeConverter() {
super(GenderType.class);
}
}