Java 枚举、JPA 和 Postgres 枚举——如何让它们一起工作?

Posted

技术标签:

【中文标题】Java 枚举、JPA 和 Postgres 枚举——如何让它们一起工作?【英文标题】:Java Enums, JPA and Postgres enums - How do I make them work together? 【发布时间】:2010-10-25 12:06:14 【问题描述】:

我们有一个带有 postgres 枚举的 postgres 数据库。我们开始将 JPA 构建到我们的应用程序中。我们也有反映 postgres 枚举的 Java 枚举。现在最大的问题是如何让 JPA 一方面理解 Java 枚举,另一方面理解 postgres 枚举? Java 方面应该相当容易,但我不知道如何做 postgres 方面。

【问题讨论】:

我们现在也将系统迁移到 JPA,但是我们有映射到 varchar 列的 java 枚举仍然没有问题与 Hibernate 【参考方案1】:

我实际上一直在使用一种比使用 PGObject 和转换器更简单的方法。由于在 Postgres 中枚举很自然地转换为文本,你只需要让它做它最擅长的事情。如果他不介意,我将借用 Arjan 的情绪示例:

Postgres 中的枚举类型:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

Java 中的类和枚举:

public @Entity class Person 

  public static enum Mood sad, ok, happy;

  @Enumerated(EnumType.STRING)
  Mood mood;

@Enumerated 标签表示枚举的序列化/反序列化应该在文本中完成。没有它就用int,比什么都麻烦。

此时您有两个选择。你要么:

    stringtype=unspecified 添加到连接字符串,如 JDBC connection parameters 中所述。这让 Postgres 猜测右侧类型并充分转换所有内容,因为它接收到类似“enum = unknown”的内容,这是一个它已经知道如何处理的表达式(将 ? 值提供给左侧的类型反序列化器)。 这是首选选项,因为它应该适用于所有简单的 UDT,例如枚举。

    jdbc:postgresql://localhost:5432/dbname?stringtype=unspecified
    

或者:

    创建从 varchar 到数据库中枚举的隐式转换。因此,在第二种情况下,数据库接收到一些赋值或比较,例如“enum = varchar”,它在其内部目录中找到一条规则,说明它可以通过 varchar 的序列化函数传递右手值,然后是反序列化函数枚举。这比应该需要的步骤多;并且在目录中有太多的隐式转换会导致任意查询产生模棱两可的解释,所以要谨慎使用它。演员阵容是:

    CREATE CAST (CHARACTER VARYING as mood) with INOUT 作为隐含的;

应该只使用它。

【讨论】:

这是迄今为止实现的最简单的选项。 当枚举用作 JPA 存储库的参数时,CREATE CAST 解决方案似乎不起作用。例如。 Entity findByMyEnum(MyEnum myEnum) 使用 auto-ddl 这仍然被创建为整数/字符变化。【参考方案2】:

这涉及制作多个映射。

首先,JDBC 驱动程序将 Postgres 枚举作为 PGObject 类型的实例返回。这个的 type 属性有你的 postgres 枚举的名字,而 value 属性有它的值。 (但是序数没有被存储,所以从技术上讲,它不再是一个枚举,因此可能完全没用)

无论如何,如果你在 Postgres 中有这样的定义:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

那么结果集将包含一个类型为“mood”且值为“happy”的 PGObject,对应于具有此枚举类型的列和值为“happy”的行。

接下来要做的是编写一些拦截器代码,该代码位于 JPA 从原始结果集中读取并设置实体值的位置之间。例如。假设您在 Java 中有以下实体:

public @Entity class Person public static enum Mood sad, ok, happy @Id Long ID; Mood mood;

不幸的是,JPA 没有提供一个简单的拦截点,您可以在其中进行从 PGObject 到 Java 枚举 Mood 的转换。然而,大多数 JPA 供应商对此都有一些专有支持。例如,Hibernate 对此有 TypeDef 和 Type 注释(来自 Hibernate-annotations.jar)。

@TypeDef(name="myEnumConverter", typeClass=MyEnumConverter.class) public @Entity class Person public static enum Mood sad, ok, happy @Id Long ID; @Type(type="myEnumConverter") Mood mood;

这些允许您提供进行实际转换的 UserType 实例(来自 Hibernate-core.jar):

public class MyEnumConverter implements UserType private static final int[] SQL_TYPES = new int[]Types.OTHER; public Object nullSafeGet(ResultSet arg0, String[] arg1, Object arg2) throws HibernateException, SQLException Object pgObject = arg0.getObject(X); // X is the column containing the enum try Method valueMethod = pgObject.getClass().getMethod("getValue"); String value = (String)valueMethod.invoke(pgObject); return Mood.valueOf(value); catch (Exception e) e.printStackTrace(); return null; public int[] sqlTypes() return SQL_TYPES; // Rest of methods omitted

这不是一个完整的工作解决方案,而只是一个指向正确方向的快速指针。

【讨论】:

>我们现在也将系统迁移到 JPA,但是我们有映射到 varchar 列的 java 枚举仍然没有问题 Hibernate varchars 在 DB 中看起来不错,但是与真正的枚举相比,你错过了 2 件事在数据库中: * 类型安全,除非您想为每一列添加检查约束或使用 FK 到一个单独的表,该表将所有 varchar 值作为 PK。 * 速度。字符串查找和比较速度较慢,即使在索引时也是如此。所以无论如何它都是一个权衡。要么您使用 varchars,但上述限制除外,或者您使用本机 PG 枚举并接受非自动映射。 附注还有第三种方式,按序数映射,但不是很推荐;使代码难以阅读变得脆弱;如果您或从 Java 枚举中删除单个常量,则数据库中已经存在的所有序数都可能无效! 是否有理由使用反射而不是投射到PgObject? @Martin 防止编译时依赖。并非每个项目的类路径中都有驱动程序,例如通常驱动程序位于服务器的某个 /lib 文件夹中。如果这对您来说没有问题,则不必使用反射。 虽然它不使用序数,但它不存储可以固定最多 63 个字节的整个字符串。枚举值的大小为 4 个字节。查看链接末尾的实现细节(postgresql.org/docs/current/datatype-enum.html)【参考方案3】:

我提交了一份包含 Hibernate 补丁的错误报告:HHH-5188

该补丁适用于我使用 JPA 将 PostgreSQL 枚举读入 Java 枚举。

【讨论】:

这似乎在 4.3.5 中被破坏得更糟。现在我什至无法将枚举持久保存到 Postgres,因为 Hibernate 似乎坚持将其写为文本或整数。【参考方案4】:

我尝试了上述建议但没有成功

我唯一能做的就是将 POSTGRES def 设置为 TEXT 类型,并在实体中使用 @Enumerated(STRING) 注释。

前(在 Kotlin 中):

CREATE TABLE IF NOT EXISTS some_example_enum_table
(
    id                      UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    some_enum               TEXT NOT NULL,
);

示例 Kotlin 类:

enum class SomeEnum  HELLO, WORLD 

@Entity(name = "some_example_enum_table")
data class EnumExampleEntity(
    @Id
    @GeneratedValue
    var id: UUID? = null,

    @Enumerated(EnumType.STRING)
    var some_enum: SomeEnum = SomeEnum.HELLO,
)

然后我的 JPA 查找确实有效:

@Repository
interface EnumExampleRepository : JpaRepository<EnumExampleEntity, UUID> 
    fun countBySomeEnumNot(status: SomeEnum): Int

【讨论】:

以上是关于Java 枚举、JPA 和 Postgres 枚举——如何让它们一起工作?的主要内容,如果未能解决你的问题,请参考以下文章

postgres:通过 varchar 搜索,这是 java 中的枚举

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

在运行时从 db 枚举为 jpa 枚举生成值

PostgreSQL 枚举和 Java 枚举之间的休眠映射

TypeORM 中的 Postgres 枚举

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