问题
环境:一个枚举(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开始,导致每次都错位。
所以在枚举类中加入了自定义的实体转换器。
后来第二个枚举又出现问题了,决定写个共用的自定义实体转换器,调用即可。
使用:子枚举直接继承这个父类的实体转换器方法就行。