JPA处理Enum类型

CY 2018年11月15日 250次浏览

在写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了。

例如下面这样,实现了JPAAttributeConverter接口

@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);
    }
}