Java 列舉、JPA 和 PostgreSQL 列舉

banq發表於2024-06-24

在本教程中,我們將探討 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<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-utils-hibernate-55</artifactId>
    <version>3.7.0</version>
</dependency>/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列舉型別。