JPA 枚举类型映射。最佳方法

Posted

技术标签:

【中文标题】JPA 枚举类型映射。最佳方法【英文标题】:JPA enumerated types mapping. Best approach 【发布时间】:2013-04-14 22:50:46 【问题描述】:

像往常一样,Java 枚举类型有相应的代码和名称描述。 包含此类字段的 Java 类将它们包含为 Enum:

public enum MyEnum
    SOMEINSTANCE(1, "test1"),
    SOMEINSTANCE(2, "test2");

    private final int code;
    private final String name;
    private MyEnum(int code, String name)
        this.code = code;
        this.name = name;
    
    ... helper getter for code and name


@Entity
puclic class EnumHolder
    private MyEnum myEnum;

我是 JPA 的新手,但我希望拥有 'myEnums' 表,看起来像:

code int not null, name varchar(50) not null)

在我的 enumHolder 表中,我希望有指向 myEnums 表的 myEnumCode 字段。

使用 currenlty 同时支持 EnumType.ORDINAL 和 EnumType.STRING 我想这不是一个好主意。

还有一个问题。如何使用Java MyEnum 类数据填写myEnums 表?你会怎么做?请最好的方法。

PS:我可以提供以下解决方案:

假设有一个表myEnumcode 和名称fields。 Java MyEnum 枚举,在问题中有所描述。 enumHolder 表需要有 myEnumCode 引用 myEnum.code 字段。如果您不同意,请评论解决方案。

@Entity
@Access(AccessType.FIELD)
public class EnumHolder 
    @Id private int id;
    @Transient private MyEnum myEnum;
    …
    public int getId()  return id; 
    public void setId(int id)  this.id = id; 
    public MyEnum getMyEnum()  return MyEnum; 
    public void setMyEnum(MyEnum myEnum)  this.myEnum = myEnum; 

    @Access(AccessType.PROPERTY) @Column(name="myEnumCode")
    protected int getMyEnumForDb() 
        return myEnum.getCode();
    

    protected void setMyEnumForDb(int enumCode) 
        myEnum = MyEnum.getByCode( enumCode);
    
…

当然这里也有缺点。但目前我看不到更好的方法。请不要提供 EnumType.ORDINAL 和 EnumType.STRING 的替代品。我不想在这里写下它的使用可能存在的所有问题(在 Effective Java 中,它是关于序数用法的描述)。使用 EnumType.STRING 我不喜欢这两种方法,因为它不允许在数据库中有描述并从 db 请求它。

关于填充数据库。我认为编写一个脚本并不难,它清除myEnum 表,然后为每个Java 枚举实例插入表中。并且始终在部署阶段执行此操作。

【问题讨论】:

在数据库中存储已经在枚举本身中硬编码的值有什么意义?数据库中的数据无论如何都不会被应用程序使用,并且您最终会在数据库中的值和枚举中硬编码的值之间出现不一致。 我可能需要生成一些报告的sql查询,因此将值及其描述存储在db中非常方便。与序数类相比,在 Java 代码中访问枚举也有其好处。 【参考方案1】:

最好的方法是将唯一的 ID 映射到每个枚举类型,从而避免 ORDINAL 和 STRING 的陷阱。请参阅此post,其中概述了映射枚举的 5 种方法。

摘自以上链接:

1&2。使用@Enumerated

目前有 2 种方法可以使用 @Enumerated 注释在 JPA 实体中映射枚举。不幸的是,EnumType.STRING 和 EnumType.ORDINAL 都有其局限性。

如果您使用 EnumType.String,那么重命名您的枚举类型之一将导致您的枚举值与保存在数据库中的值不同步。如果您使用 EnumType.ORDINAL,那么删除或重新排序枚举中的类型将导致保存在数据库中的值映射到错误的枚举类型。

这两个选项都很脆弱。如果枚举在未执行数据库迁移的情况下被修改,您可能会危及数据的完整性。

3.生命周期回调

一个可能的解决方案是使用 JPA 生命周期回调注解,@PrePersist 和 @PostLoad。这感觉非常难看,因为您的实体中现在将有两个变量。一个映射存储在数据库中的值,另一个映射实际的枚举。

4.将唯一 ID 映射到每个枚举类型

首选的解决方案是将您的枚举映射到枚举中定义的固定值或 ID。映射到预定义的固定值使您的代码更加健壮。对枚举类型顺序的任何修改,或者对名称的重构,都不会造成任何不良影响。

5.使用 Java EE7 @Convert

如果您使用的是 JPA 2.1,您可以选择使用新的 @Convert 注释。这需要创建一个使用@Converter 注释的转换器类,您可以在其中定义每种枚举类型将哪些值保存到数据库中。然后,在您的实体中,您将使用 @Convert 注释您的枚举。

我的偏好:(第 4 位)

我更喜欢在枚举中定义我的 ID 而不是使用转换器的原因是良好的封装。只有枚举类型应该知道它的 ID,只有实体应该知道它如何将枚举映射到数据库。

代码示例见原文post。

【讨论】:

另一个关于最佳方法的 opinion 支持 JPA 2.1 AttributeConverter 方法(上面的#5)。 选项 4 现在是我的默认选项。很简单。谢谢!【参考方案2】:

您可以使用@Enumerated 向您的实体添加枚举字段。这里的关键是你想吃蛋糕也想吃——为了保持一致,最好选择EnumType.ORDINALEnumType.STRING

两者各有优缺点,列于at this site。最大的区别似乎在于排序 - 如果您设置了 EnumType.ORDINAL,那么在更新枚举时会遇到问题。

我会鼓励你重新考虑你想要设计你的桌子的方式。字符串和整数本质上存储了相同的信息 - 枚举值 是什么 - 您可以从数据库中获取这些信息而无需大惊小怪。最好将id 作为表上的主键,然后将枚举类型作为字符串或序数值,考虑到上面链接中的注意事项。

【讨论】:

【参考方案3】:
@Column(name = "MY_TYPE")
@Enumerated(EnumType.STRING)
private MyType type;

enum MyType 
type_1
type_2

然后创建您的列,如下所示:

TYPE_ID  VARCHAR(20) NOT NULL DEFAULT 'TYPE_1' CHECK (TYPE_ID IN ('TYPE_1', 'TYPE_2')))

这解决了我的问题。

【讨论】:

以上是关于JPA 枚举类型映射。最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JPA 和 Hibernate 映射 PostgreSQL 枚举

Hibernate学习笔记

使用固定值映射 JPA 中的枚举?

枚举类型的命名空间 - 最佳实践

打字稿:映射类型中的枚举键

spring data jpa @Query 枚举类型不返回数据