Java 枚举、JPA 和 PostgreSQL 枚举

在本教程中,我们将探讨 Java枚举、JPA和 PostgreSQL枚举的概念,并学习如何一起使用它们在 Java枚举和 PostgreSQL枚举之间创建无缝映射。

bJava枚举/b
Java枚举是一种特殊类型的类,它表示固定数量的常量。枚举用于定义一组具有基础类型(如字符串或整数)的命名值。当我们需要在应用程序中定义一组具有特定含义的命名值时,枚举非常有用。

以下是 Java枚举的一个示例:

codepublic enum OrderStatus {
    PENDING, IN_PROGRESS, COMPLETED, CANCELED
}/code
在这个例子中,OrderStatus 枚举定义了四个常量。这些常量可以在我们的应用程序中用来表示订单的状态。

b使用@Enumerated注解/b
当通过 JPA 使用 Java枚举时,我们需要用@Enumerated注释枚举字段来指定枚举值应如何存储在数据库中。

首先,我们定义一个名为CustomerOrder 的实体类,并用@Entity注释该类以将其标记为 JPA 持久性:

code@Entity
public class CustomerOrder {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Enumerated() 
    private OrderStatus status;
    // ... other fields and methods
}/code
默认情况下,JPA 将枚举值存储为整数,表示枚举常量的序数位置。例如,如果我们有一个枚举OrderStatus,其值为PENDING、IN_PROGRESS、COMPLETED和CANCELED,则默认行为会分别将它们存储为整数 0、1、2 和 3。生成的数据库表将具有smallint 类型的 状态列,其值为 0 到 3:

codecreate table customer_order (
    id bigserial not null,
    status smallint check (status between 0 and 3),
    primary key (id)
);/code
但是,如果我们更改 Java 代码中枚举常量的顺序,这种默认行为可能会导致问题。例如,如果我们交换IN_PROGRESS和PENDING的顺序,数据库值仍将是 0、1、2 和 3,但它们将不再与更新后的枚举顺序匹配。这可能会导致我们的应用程序出现不一致和错误。

为了避免这个问题,我们可以使用EnumType.STRING将枚举值作为字符串存储在数据库中。这种方法确保枚举值以人类可读的格式存储,并且我们可以更改枚举常量的顺序而不影响数据库值:

code@Enumerated(EnumType.STRING)
private OrderStatus status;/code
这指示 JPA在数据库中存储OrderStatus枚举值的字符串表示形式(例如“ PENDING ”),而不是序数位置(整数索引)。生成的数据库表将具有varchar类型的状态列,该列可以保存枚举中定义的特定字符串值:

codecreate table customer_order (
    id bigint not null,
    status varchar(16) check (status in ('PENDING','IN_PROGRESS', 'COMPLETED', 'CANCELLED')),
    primary key (id)
);/code

b 将 Java 枚举映射到 PostgreSQL 枚举的挑战/b
由于Java枚举和 PostgreSQL 枚举在处理和功能上的差异,将 Java 枚举映射到 PostgreSQL 枚举可能具有挑战性。即使使用EnumType.STRING,JPA 仍然不知道如何将 Java枚举映射到 PostgreSQL 枚举。为了演示这个问题,让我们创建一个 PostgreSQL 枚举类型:

codeCREATE TYPE order_status AS ENUM ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'CANCELED');/code

接下来,我们创建一个使用 PostgreSQL 枚举类型的表:

codeCREATE TABLE customer_order (
    id BIGINT NOT NULL,
    status order_status,
    PRIMARY KEY (id)
);/code
我们已更新status列以使用 PostgreSQL 枚举类型order_status。现在,让我们尝试将一些数据插入表中:

codeCustomerOrder order = new CustomerOrder();
order.setStatus(OrderStatus.PENDING);
session.save(order);/code

然而,当我们尝试插入数据时,我们会收到一个异常:

codeorg.hibernate.exception.SQLGrammarException: could not execute statement 
  ERROR: column "status" is of type order_status but expression is of type character varying/code

发生 SQLGrammarException是因为 JPA 不知道如何将 Java枚举 OrderStatus映射到 PostgreSQL 枚举order_status。

b使用@Type注释/b
在 Hibernate 5 中,我们可以利用 Hypersistence Utils 库来解决这一挑战。该库提供了其他类型,包括对 PostgreSQL 枚举的支持。

首先,我们需要将Hypersistence Utils依赖项添加到我们的pom.xml中:

code
    io.hypersistence
    hypersistence-utils-hibernate-55
    3.7.0
/code
Hypersistence Utils 库包含一个PostgreSQLEnumType类,用于处理 Java 枚举和 PostgreSQL 枚举类型之间的转换。我们将使用此类作为我们的自定义类型处理程序。

接下来,我们可以使用@Type注释注释枚举字段,并在实体类中定义自定义类型处理程序:

code@Entity
@TypeDef(
    name = "pgsql_enum",
    typeClass = PostgreSQLEnumType.class
)
public class CustomerOrder {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Long id;
    @Enumerated(EnumType.STRING)
    @Column(columnDefinition = "order_status")
    @Type(type = "pgsql_enum")
    private OrderStatus status;
    // ... other fields and methods
}/code
通过遵循这些步骤,我们可以有效地将 PostgreSQL 枚举类型映射到 Hibernate 5 中的 Java 枚举。

b使用PostgreSQLEnumJdbcType/b
在 Hibernate 6 中,我们可以在枚举字段上直接使用@JdbcType注释来指定自定义 JDBC 类型处理程序。此注释允许我们定义自定义JdbcType类来处理 Java枚举类型与相应 JDBC 类型之间的映射。

以下是我们如何在CustomerOrder实体中使用@JdbcType:

code@Enumerated(EnumType.STRING)
@JdbcType(type = PostgreSQLEnumJdbcType.class)
private OrderStatus status;/code
我们指定PostgreSQLEnumJdbcType类作为自定义类型处理程序。此类是 Hibernate 6 的一部分,负责处理 Java枚举字符串和 PostgreSQL 枚举类型之间的转换。

当我们持久化OrderStatus对象(例如order.setStatus(OrderStatus.PENDING))时,Hibernate 首先将枚举值转换为其字符串表示形式(“ PENDING ”)。然后,PostgreSQLEnumJdbcType类获取字符串值(“ PENDING ”)并将其转换为适合 PostgreSQL 枚举类型的格式。然后将转换后的值传递到数据库以存储在order_status列中。

b使用本机查询插入枚举值/b
使用原生查询将数据插入 PostgreSQL 表时,插入数据的类型必须与列类型匹配。对于枚举类型列,PostgreSQL 要求值是枚举类型,而不仅仅是纯字符串。 

让我们尝试使用不带强制转换的本机查询:

codeString sql = "INSERT INTO customer_order (status) VALUES (:status)";
Query query = session.createNativeQuery(sql);
query.setParameter("status", OrderStatus.COMPLETED); // Use the string representation of the enum/code

我们得到以下错误:

codeorg.postgresql.util.PSQLException: ERROR: column "status" is of type order_status but expression is of type character varying/code

出现此错误的原因是 PostgreSQL 期望该值为order_status类型,但收到的却是字符变化。为了解决此问题,我们在本机查询中将值显式转换为枚举类型:

codeString sql = "INSERT INTO customer_order (status) VALUES (CAST(:status AS order_status))";
Query query = session.createNativeQuery(sql);
query.setParameter("status", OrderStatus.COMPLETED);/code

SQL 语句中的CAST (:status AS order_status)部分能确保:字符串值“ COMPLETED ”被明确转换为PostgreSQL 中的order_status枚举类型。