学习笔记 – JPA出现的数据库枚举映射的问题以及一步步优化

问题

环境:一个枚举(name,id),数据库只存枚举的id。

当我们从数据库取出这个id对应的整条记录时,JPA会帮助我们对枚举自动映射(id到对应的枚举)。

今天这个地方出错了,id总是映射到错误的枚举上。

解决

1,仅传递枚举名,这样不需要映射。但是对未来修改和扩展有非常非常大的问题。

2,编写工具类xxxEnumUtils。

逻辑:我们可以每次调用工具类,然后手动映射传回去。

操作:遍历枚举的value,对比每个id,相同则返回这个枚举。

缺点:同时多个枚举不能共用同一个,实现在下面。

3,现在的解决方法

大部分情况下,我们需要检查@Enumerated()内的东西。

JPA提供给我们两种枚举映射的方法。

EnumType.Ordinal:

按照顺序,数据库存的是枚举的id。

这玩意有个缺点,一定是按顺序的,我们没办法定义。

所以队友可能会在枚举中间加了个新枚举,导致整体id序列化错误(多一位)。

EnumType.Spring:

存的是枚举的名字,和第一种解决方法一样,我们没办法维护他,就是不能改枚举名。

所以这两种自带的枚举都有非常多的问题,这样我们的解决方法就出现了。

自定义一个枚举转换器,来实现自动转换。

这里我们就可以找到实体转换器,进行自定义转换。

public class EnumConvert implements AttributeConverter<StatusEnum, Integer> {
    @Override
    public Integer convertToDatabaseColumn(StatusEnum attribute) {
        return attribute.getValue();
    }

    @Override
    public StatusEnum convertToEntityAttribute(Integer dbData) {
        return StatusEnum.fromValue(dbData);
    }
}

但是我们肯定不能因为每个枚举都写一个转换器吧。

所以我们需要实现一种公有的转换器。

这时候就利用泛型。

我们肯定要先写一个接口。

public interface IBaseDbEnum {
    /**
     * 用于显示的枚举名
     *
     * @return
     */
    String getDisplay();

    /**
     * 存储到数据库的枚举值
     *
     * @return
     */
    Integer getValue();

    //按枚举的value获取枚举实例
    static <T extends IBaseDbEnum> T fromValue(Class<T> enumType, Integer value) {
        for (T object : enumType.getEnumConstants()) {
            if (Objects.equals(value, object.getValue())) {
                return object;
            }
        }
        throw new IllegalArgumentException("No enum value " + value + " of " + enumType.getCanonicalName());
    }
}

然后按照原来的方式,利用泛型实现

public class EnumConvert<T extends IBaseDbEnum> implements AttributeConverter<T, Integer> {
    @Override
    public Integer convertToDatabaseColumn(T attribute) {
        return attribute.getValue();
    }

    @Override
    public T convertToEntityAttribute(Integer dbData) {
        //先随便写,测试一下
        return (T) StatusEnum.Active;
    }
}

这样就解决问题了。

实体转换器:实现很简单,只需要实现两个接口就好。

关于项目优化的过程

最开始出现映射失误,以为没有加@Enumerated注解(实际原因不是,因为发现默认就是ordinal)。

考虑到后续spring扩展性很垃圾,所以采用ordinal了。

但是发现还是出错,排查后发现是因为ordinal是不看id的,只看顺序,原来定义枚举时从1开始,导致每次都错位。

所以在枚举类中加入了自定义的实体转换器。

后来第二个枚举又出现问题了,决定写个共用的自定义实体转换器,调用即可。

使用:子枚举直接继承这个父类的实体转换器方法就行。

正文完