Java 枚舉、JPA 和 PostgreSQL 枚舉
一、簡介
在本教程中,我們將探討 Java enum 、JPA 和 PostgreSQL enum的概念,並學習如何一起使用它們來創建 Java enum和 PostgreSQL enum之間的無縫映射。
2.Java enum
Java enum是一種特殊類型的類,表示固定數量的常數。枚舉用於定義一組具有基礎類型的命名值,例如字串或整數。當我們需要定義一組在應用程式中具有特定含義的命名值時,枚舉非常有用。
下面是 Java enum的範例:
public enum OrderStatus {
PENDING, IN_PROGRESS, COMPLETED, CANCELED
}
在此範例中, OrderStatus enum定義了四個常數。這些常數可以在我們的應用程式中使用來表示訂單的狀態。
3.使用@Enumerated註解
當在 JPA 中使用 Java enum時,我們需要使用@Enumerated註解 enum 字段,以指定 enum 值如何儲存在資料庫中。
首先,我們定義一個名為CustomerOrder實體類,並使用@Entity註解來將其標記為 JPA 持久性:
@Entity
public class CustomerOrder {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated()
private OrderStatus status;
// ... other fields and methods
}
預設情況下,JPA 將枚舉值儲存為整數,表示枚舉常數的序號位置。例如,如果我們有一個枚舉OrderStatus ,其值為PENDING 、 IN_PROGRESS 、 COMPLETED和CANCELED ,則預設行為會將它們分別儲存為整數 0、1、2 和 3。產生的資料庫表將有一個類型為smallint的status列,其值為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;
這指示 JPA 將OrderStatus枚舉值的字串表示形式(例如「 PENDING 」)儲存在資料庫中,而不是序數位置(整數索引)。產生的資料庫表將有一個varchar類型的狀態列,它可以保存在枚舉中定義的特定字串值:
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 枚舉的挑戰
將 Java enum映射到 PostgreSQL 枚舉可能具有挑戰性,因為它們的處理和功能存在差異。即使使用EnumType.STRING ,JPA 仍然不知道如何將 Java enum對應到 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 enum OrderStatus對應到 PostgreSQL 枚舉order_status 。
5.使用@Type註解
在 Hibernate 5 中,我們可以利用 Hypersistence Utils 函式庫來應對這項挑戰。該程式庫提供了其他類型,包括對 PostgreSQL 枚舉的支援。
首先,我們需要將Hypersistence Utils依賴項加入pom.xml中:
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-55</artifactId>
<version>3.7.0</version>
</dependency>
Hypersistence Utils 函式庫包含一個PostgreSQLEnumType類,用於處理 Java 枚舉和 PostgreSQL 枚舉類型之間的轉換。我們將使用此類作為自訂類型處理程序。
接下來,我們可以使用@Type註解來註解枚舉字段,並在實體類別中定義自訂類型處理程序:
@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
}
透過遵循這些步驟,我們可以有效地將 PostgreSQL 枚舉類型對應到 Hibernate 5 中的 Java 枚舉。
6. 使用PostgreSQLEnumJdbcType
在 Hibernate 6 中,我們可以直接在枚舉欄位上使用@JdbcType註解來指定自訂 JDBC 類型處理程序。這個註解允許我們定義一個自訂的JdbcType類別來處理 Java enum類型和對應的 JDBC 類型之間的對應。
以下是我們如何在CustomerOrder實體使用@JdbcType :
@Enumerated(EnumType.STRING)
@JdbcType(type = PostgreSQLEnumJdbcType.class)
private OrderStatus status;
我們將PostgreSQLEnumJdbcType類別指定為自訂類型處理程序。該類別是 Hibernate 6 的一部分,負責處理 Java enum字串和 PostgreSQL 枚舉類型之間的轉換。
當我們持久化一個OrderStatus物件(例如order.setStatus(OrderStatus.PENDING) )時,Hibernate 首先將枚舉值轉換為其字串表示形式(「 PENDING 」)。然後, PostgreSQLEnumJdbcType類別取得字串值(「 PENDING 」)並將其轉換為適合 PostgreSQL 枚舉類型的格式。然後,轉換後的值將傳遞到資料庫並儲存在order_status列中。
7. 使用本機查詢插入枚舉值
使用本機查詢將資料插入 PostgreSQL 表時,插入的資料類型必須與資料列類型相符。對於枚舉類型列,PostgreSQL 期望值是枚舉類型,而不僅僅是普通字串。
讓我們嘗試使用不進行轉換的本機查詢:
String 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
我們得到以下錯誤:
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枚舉類型。
八、結論
在本文中,我們探討如何使用 JPA 在 Java enum和 PostgreSQL 枚舉之間進行對應。透過使用PostgreSQLEnumJdbcType ,我們確保 Java enum和 PostgreSQL 枚舉之間的無縫整合。
與往常一樣,範例的原始程式碼可在 GitHub 上取得。 Hibernate 5 範例的原始碼可在 GitHub 上取得。