1、简介 {#1简介}
本文将带你了解 Java 枚举、JPA 和 PostgreSQL 枚举的概念,以及如何将它们结合使用,在 Java 枚举和 PostgreSQL 枚举之间创建无缝映射。
2、Java 枚举 {#2java-枚举}
Java 枚举(Enum
)是一种特殊类型的类,用于表示一组固定数量的常量。枚举用于定义一组具有底层类型(如字符串或整数)的命名值。当我们需要定义一组在应用中具有特定含义的命名值时,枚举非常有用。
下面是一个 Java 枚举的示例:
public enum OrderStatus {
PENDING, IN_PROGRESS, COMPLETED, CANCELED
}
在本例中,OrderStatus
枚举定义了四个常量。这些常量可以在我们的应用中用来表示订单的状态。
3、使用 @Enumerated 注解 {#3使用-enumerated-注解}
在 JPA 中使用 Java 枚举时,需要用 @Enumerated
来注解枚举字段,以指定如何将枚举值存储到数据库中。
首先,定义一个名为 CustomerOrder
的实体类,并用 @Entity
进行注解,以标记其用于 JPA 持久化:
@Entity
public class CustomerOrder {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated()
private OrderStatus status;
// 其他的字段和方法省略
}
默认情况下,JPA 将枚举值存储为整数(Integer),代表枚举常量的序号位置(ordinal()
)。例如,上文中的 OrderStatus
枚举值 PENDING
、IN_PROGRESS
、COMPLETED
和 CANCELED
,默认行为将分别把它们存储为整数 0
、1
、2
和 3
。由此产生的数据库表将有一个小 int
类型的状态列,其值为 0
至 3
:
create table customer_order (
id bigserial not null,
status smallint check (status between 0 and 3),
primary key (id)
);
但是,如果我们在 Java 代码中更改枚举常量的顺序,这种默认行为可能会导致问题。例如,如果我们调换 IN_PROGRESS
和 PENDING
的顺序,数据库值仍将是 0
、1
、2
和 3
,但它们将不再与更新后的枚举顺序相匹配。这会导致应用出现不一致和错误。
为了避免这个问题,可以使用 EnumType.STRING
将枚举值以字符串形式存储在数据库中。这种方法可确保枚举值以人类可读的格式存储,而且可以更改枚举常量的顺序,而不会影响数据库值:
@Enumerated(EnumType.STRING)
private OrderStatus status;
@Enumerated(EnumType.STRING)
指示 JPA 在数据库中存储 OrderStatus
枚举值的字符串表示(如 "PENDING"
),而不是顺序位置(整数索引)。生成的数据库表将有一个 varchar
类型的 status
列,该列可以保存枚举中定义的特定字符串值:
create table customer_order (
id bigint not null,
status varchar(16) check (status in ('PENDING','IN_PROGRESS', 'COMPLETED', 'CANCELLED')),
primary key (id)
);
4、Java 枚举映射到 PostgreSQL 枚举 {#4java-枚举映射到-postgresql-枚举}
由于处理和功能上的差异,将 Java 枚举映射到 PostgreSQL 枚举可能有点棘手。即使使用 EnumType.STRING
,JPA 仍然不知道如何将 Java 枚举映射到 PostgreSQL 枚举。
为了演示这个问题,创建一个 PostgreSQL 枚举类型:
CREATE TYPE order_status AS ENUM ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'CANCELED');
接着,创建一个使用 PostgreSQL
枚举类型的表:
CREATE TABLE customer_order (
id BIGINT NOT NULL,
status order_status,
PRIMARY KEY (id)
);
我们更新了 status
列,使用 PostgreSQL 枚举类型 order_status
。现在,尝试向表中插入一些数据:
CustomerOrder order = new CustomerOrder();
order.setStatus(OrderStatus.PENDING);
session.save(order);
尝试插入数据时,会出现异常:
org.hibernate.exception.SQLGrammarException: could not execute statement
[ERROR: column "status" is of type order_status but expression is of type character varying
出现 SQLGrammarException
是因为 JPA 不知道如何将 Java 枚举 OrderStatus
映射到 PostgreSQL 枚举 order_status
。
5、使用 @Type 注解 {#5使用-type-注解}
在 Hibernate 5 中,我们可以利用 Hypersistence
Utils 库来处理这个问题。该库提供了更多类型,包括对 PostgreSQL 枚举的支持。
首先,需要在 pom.xml
中添加 Hypersistence
Utils 依赖:
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-55</artifactId>
<version>3.7.0</version>
</dependency>
Hypersistence
Utils 库包含一个 PostgreSQLEnumType
类,用于处理 Java 枚举和 PostgreSQL 枚举类型之间的转换。我们将使用该类作为自定义 Type Handler。
接下来,我们可以使用 @Type
来注解枚举字段,并在实体类中定义 Type Handler:
@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;
// 其他字段和方法省略
}
通过如上步骤,我们就能在 Hibernate 5 中有效地将 PostgreSQL 枚举类型映射为 Java 枚举。
6、使用 PostgreSQLEnumJdbcType {#6使用-postgresqlenumjdbctype}
在 Hibernate 6 中,我们可以直接在枚举字段上使用 @JdbcType
注解来指定自定义 JDBC Type Handler。该注解允许我们定义一个自定义的 JdbcType
类,以处理 Java 枚举类型和相应 JDBC 类型之间的映射。
在 CustomerOrder
实体中使用 @JdbcType
,如下:
@Enumerated(EnumType.STRING)
@JdbcType(type = PostgreSQLEnumJdbcType.class)
private OrderStatus status;
如上,指定 PostgreSQLEnumJdbcType
类作为自定义 Type Handler。该类是 Hibernate 6 的一部分,负责处理 Java 枚举字符串和 PostgreSQL 枚举类型之间的转换。
当持久化一个 OrderStatus
对象时(例如 order.setStatus(OrderStatus.PENDING)
),Hibernate 首先将枚举值转换为字符串表示("PENDING"
)。然后,PostgreSQLEnumJdbcType
类将字符串值("PENDING"
)转换为适合 PostgreSQL 枚举类型的格式。然后将转换后的值传递给数据库,存储在 order_status
列中。
7、原生查询插入枚举值 {#7原生查询插入枚举值}
使用原生查询将数据插入 PostgreSQL 表时,插入的数据类型必须与列类型相匹配。对于枚举类型的列,PostgreSQL 希望值是枚举类型的,而不仅仅是普通字符串。
试着执行不带转换的原生查询:
String sql = "INSERT INTO customer_order (status) VALUES (:status)";
Query query = session.createNativeQuery(sql);
query.setParameter("status", OrderStatus.COMPLETED); // 用字符串表示枚举
错误信息如下:
org.postgresql.util.PSQLException: ERROR: column "status" is of type order_status but expression is of type character varying
出现这个错误的原因是,PostgreSQL 希望值是 order_status
类型,但接收到的却是 character varying
。为了解决这个问题,可以在原生查询中明确地将值转换为枚举类型:
String sql = "INSERT INTO customer_order (status) VALUES (CAST(:status AS order_status))";
Query query = session.createNativeQuery(sql);
query.setParameter("status", OrderStatus.COMPLETED);
SQL 语句中的 CAST(:status AS order_status)
部分确保字符串值 "COMPLETED"
在 PostgreSQL 中被明确转换为 order_status
枚举类型。
8、总结 {#8总结}
本文介绍了如何使用 JPA 在 Java 枚举和 PostgreSQL 枚举之间进行映射,通过使用 PostgreSQLEnumJdbcType 可以确保 Java 枚举和 PostgreSQL 枚举之间的无缝集成。
Ref:https://www.baeldung.com/java-enums-jpa-postgresql