在Java和PostgreSQL枚舉之間轉(zhuǎn)換的通用方法
枚舉類型(enum)是一種方便的數(shù)據(jù)類型,允許我們指定一個常量列表,對象字段或數(shù)據(jù)庫列可以設(shè)置為該列表中的值。
枚舉的美妙之處在于我們可以通過提供人類可讀格式的枚舉常量來確保數(shù)據(jù)完整性。因此,Java和PostgreSQL原生支持這種數(shù)據(jù)類型并不令人驚訝。
但是,Java和PostgreSQL枚舉之間的轉(zhuǎn)換并不是開箱即用的。JDBC API不將枚舉識別為一個獨(dú)特的數(shù)據(jù)類型,這使得如何處理轉(zhuǎn)換的決策留給了JDBC驅(qū)動程序。通常,驅(qū)動程序?qū)Υ藷o能為力 —— 這是一個先有雞還是先有蛋的問題。
有許多解決方案可以幫助您在Java和PostgreSQL枚舉之間進(jìn)行映射,但大多數(shù)解決方案都是ORM或JDBC特定的。這意味著適用于Spring Data的建議可能不適用于Quarkus,反之亦然。
在本文中,我將回顧處理Java和PostgreSQL枚舉轉(zhuǎn)換的通用方法。此方法適用于普通的JDBC API和流行的ORM框架,如Spring Data、Hibernate、Quarkus和Micronaut。此外,它還被構(gòu)建在PostgreSQL上的數(shù)據(jù)庫支持,包括Amazon Aurora、Google AlloyDB和YugabyteDB。
創(chuàng)建Java實(shí)體對象和枚舉
假設(shè)我們有一個代表披薩訂單的Java實(shí)體對象:
public class PizzaOrder {
private Integer id;
private OrderStatus status;
private Timestamp orderTime;
// getters and setters are omitted
}
該對象的status字段是一個如下定義的枚舉類型:
public enum OrderStatus {
Ordered,
Baking,
Delivering,
YummyInMyTummy
}
當(dāng)我們在線訂購披薩時,應(yīng)用程序?qū)顟B(tài)設(shè)置為Ordered。一旦廚師開始處理我們的訂單,狀態(tài)就會變?yōu)锽aking。一旦披薩剛出爐,就會有人取走并送到我們的門口 - 狀態(tài)隨后更新為Delivering。最后,狀態(tài)被設(shè)置為YummyInMyTummy,意味著我們享受了披薩(希望如此!)
創(chuàng)建數(shù)據(jù)庫表和枚舉
為了在PostgreSQL中持久化披薩訂單,讓我們創(chuàng)建以下與我們的PizzaOrder實(shí)體類映射的表:
CREATE TABLE pizza_order ( id int PRIMARY KEY, status order_status NOT NULL, order_time timestamp NOT NULL DEFAULT now()? );
該表帶有一個名為order_status的自定義類型。該類型是如下定義的枚舉:
CREATE TYPE order_status AS ENUM( 'Ordered', 'Baking', 'Delivering', 'YummyInMyTummy');
該類型定義與 Java 對應(yīng)的常量(狀態(tài))類似。
解決轉(zhuǎn)換問題
如果我們使用psql(或其他SQL工具)連接到PostgreSQL并執(zhí)行以下INSERT語句,它將成功執(zhí)行:
insert into pizza_order (id, status, order_time) values (1, 'Ordered', now());
該語句很好地接受文本表示形式的訂單狀態(tài)(枚舉數(shù)據(jù)類型)Ordered。
看到這一點(diǎn)后,我們可能會想以這種格式將 Java 枚舉值發(fā)送到 PostgreSQL String。如果我們直接使用JDBC APIPreparedStatement ,可以如下所示:
PreparedStatement statement = conn
.prepareStatement("INSERT INTO pizza_order (id, status, order_time) VALUES(?,?,?)");
statement.setInt(1, 1);
statement.setString(2, OrderStatus.Ordered.toString());
statement.setTimestamp(3, Timestamp.from(Instant.now()));
statement.executeUpdate();
但是,該語句將出現(xiàn)以下異常:
org.postgresql.util.PSQLException: ERROR: column "status" is of type order_status but expression is of type character varying Hint: You will need to rewrite or cast the expression. Position: 60 at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2675) at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2365) at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:355) at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:490) at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:408)
盡管 PostgreSQL 在INSERT/UPDATE通過 psql 會話直接執(zhí)行語句時接受枚舉文本表示,但它不支持varchar(由 Java 傳遞)和我們的枚舉類型之間的轉(zhuǎn)換。
對于普通 JDBC API 修復(fù)此問題的一種方法是將 Java 枚舉保留為以下類型的對象java.sql.Types.OTHER:
PreparedStatement statement = conn
.prepareStatement("INSERT INTO pizza_order (id, status, order_time) VALUES(?,?,?)");
statement.setInt(1, 1);
statement.setObject(2, OrderStatus.Ordered, java.sql.Types.OTHER);
statement.setTimestamp(3, Timestamp.from(Instant.now()));
statement.executeUpdate();
但是,正如我之前所說,這種方法并不通用。雖然它適用于普通的 JDBC API,但如果您使用Spring Data、Quarkus或其他ORM,則需要尋找其他解決方案。
數(shù)據(jù)庫級別的類型轉(zhuǎn)換
數(shù)據(jù)庫提供了通用的解決方案。
PostgreSQL 支持強(qiáng)制轉(zhuǎn)換運(yùn)算符,可以自動執(zhí)行兩種數(shù)據(jù)類型之間的轉(zhuǎn)換。
因此,在我們的例子中,我們需要做的就是創(chuàng)建以下運(yùn)算符:
CREATE CAST (varchar AS order_status) WITH INOUT AS IMPLICIT;
創(chuàng)建的運(yùn)算符將在varchar類型(由 JDBC 驅(qū)動程序傳遞)和我們的數(shù)據(jù)庫級order_status枚舉類型之間進(jìn)行映射。該WITH INOUT AS IMPLICIT子句確保對于使用該order_status類型的所有語句,強(qiáng)制轉(zhuǎn)換將透明且自動地發(fā)生。
使用純 JDBC API 進(jìn)行測試
在 PostgreSQL 中創(chuàng)建該強(qiáng)制轉(zhuǎn)換運(yùn)算符后,早期的 JDBC 代碼片段會毫無問題地插入一個訂單:
PreparedStatement statement = conn
.prepareStatement("INSERT INTO pizza_order (id, status, order_time) VALUES(?,?,?)");
statement.setInt(1, 1);
statement.setString(2, OrderStatus.Ordered.toString());
statement.setTimestamp(3, Timestamp.from(Instant.now()));
statement.executeUpdate();
我們所需要做的就是將 Java 枚舉值作為 a 傳遞String,驅(qū)動程序會將其以表示形式發(fā)送到 PostgreSQL varchar,該表示形式會自動將該varchar值轉(zhuǎn)換為order_status類型。
如果您從數(shù)據(jù)庫讀回訂單,那么您可以輕松地從一個值重建 Java 級枚舉String:
PreparedStatement statement = conn.prepareStatement("SELECT id, status, order_time " +
"FROM pizza_order WHERE id = ?");
statement.setInt(1, 1);
ResultSet resultSet = statement.executeQuery();
resultSet.next();
PizzaOrder order = new PizzaOrder();
order.setId(resultSet.getInt(1));
order.setStatus(OrderStatus.valueOf(resultSet.getString(2)));
order.setOrderTime(resultSet.getTimestamp(3));
使用 Spring Data 進(jìn)行測試
接下來,讓我們使用 Spring Data 驗(yàn)證基于強(qiáng)制轉(zhuǎn)換運(yùn)算符的方法。如今,您可能會直接使用 ORM 而不是 JDBC API。
首先,我們需要PizzaOrder用一些 JPA 和 Hibernate 注釋來標(biāo)記我們的實(shí)體類:
@Entity
public class PizzaOrder {
@Id
private Integer id;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@CreationTimestamp
private Timestamp orderTime;
// getters and setters are omitted
}
指示@Enumerated(EnumType.STRING)JPA 實(shí)現(xiàn)(通常是 Hibernate)將枚舉值作為 a 傳遞String給驅(qū)動程序。
其次,我們PizzaOrderRepository使用 Spring Data API 創(chuàng)建并保存實(shí)體對象:
// The repository interface
public interface PizzaOrderRepository extends JpaRepository<PizzaOrder, Integer> {
}
// The service class
@Service
public class PizzaOrderService {
@Autowired
PizzaOrderRepository repo;
@Transactional
public void addNewOrder(Integer id) {
PizzaOrder order = new PizzaOrder();
order.setId(id);
order.setStatus(OrderStatus.Ordered);
repo.save(order);
}
...
// Somewhere in the source code
pizzaService.addNewOrder(1);
}
當(dāng)pizzaService.addNewOrder(1)我們的源代碼中的某個位置調(diào)用該方法時,訂單將被創(chuàng)建并成功保存到數(shù)據(jù)庫中。Java 和 PostgreSQL 枚舉之間的轉(zhuǎn)換不會出現(xiàn)任何問題。
最后,如果我們需要從數(shù)據(jù)庫讀回訂單,我們可以使用該JpaRepository.findById(ID id)方法,該方法根據(jù)其表示重新創(chuàng)建 Java 枚舉String:
PizzaOrder order = repo.findById(orderId).get();
?System.out.println("Order status: " + order.getStatus());
使用 Quarkus 進(jìn)行測試
Quarkus 怎么樣,它可能是您的#1 ORM?只要 Quarkus 支持 Hibernate 作為 JPA 實(shí)現(xiàn),與 Spring Data 就沒有顯著差異。
首先,我們PizzaOrder用 JPA 和 Hibernate 注解來注解我們的實(shí)體類:
@Entity(name = "pizza_order")
public class PizzaOrder {
@Id
private Integer id;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@CreationTimestamp
@Column(name = "order_time")
private Timestamp orderTime;
// getters and setters are omitted
}
其次,我們介紹PizzaOrderService一下使用EntityManager實(shí)例進(jìn)行數(shù)據(jù)庫請求:
@ApplicationScoped
public class PizzaOrderService {
@Inject
EntityManager entityManager;
@Transactional
public void addNewOrder(Integer id) {
PizzaOrder order = new PizzaOrder();
order.setId(id);
order.setStatus(OrderStatus.Ordered);
entityManager.persist(order);
}
...
// Somewhere in the source code
pizzaService.addNewOrder(1);
當(dāng)我們調(diào)用pizzaService.addNewOrder(1)應(yīng)用程序邏輯中的某處時,Quarkus 將成功保留順序,并且 PostgreSQL 將負(fù)責(zé) Java 和 PostgreSQL 枚舉轉(zhuǎn)換。
最后,要從數(shù)據(jù)庫讀回訂單,我們可以使用以下方法EntityManager將結(jié)果集中的數(shù)據(jù)映射到PizzaOrder實(shí)體類(包括枚舉字段):
PizzaOrder order = entityManager.find(PizzaOrder.class, 1);
?System.out.println("Order status: " + order.getStatus()); ?
使用 Micronaut 進(jìn)行測試
好吧,好吧, Micronaut怎么樣?我喜歡這個框架,你可能也會喜歡它。
數(shù)據(jù)庫端強(qiáng)制轉(zhuǎn)換運(yùn)算符對于 Micronaut 來說也是一個完美的解決方案。為了讓事情有所不同,我們不會為 Micronaut 使用 Hibernate。相反,我們將通過使用該模塊來依賴 Micronaut 自己的功能micronaut-data-jdbc:
<dependency>
<groupId>io.micronaut.data</groupId>
<artifactId>micronaut-data-jdbc</artifactId>
</dependency>
// other dependencies
首先,我們對PizzaOrder實(shí)體進(jìn)行注釋:
@MappedEntity
public class PizzaOrder {
@Id
private Integer id;
@Enumerated(EnumType.STRING)
private OrderStatus status;
private Timestamp orderTime;
// getters and setters are omitted
}
然后,通過在應(yīng)用程序邏輯中的某個位置調(diào)用以下代碼片段,將披薩訂單存儲在數(shù)據(jù)庫中:
PizzaOrder order = new PizzaOrder(); order.setId(1); order.setStatus(OrderStatus.Ordered); order.setOrderTime(Timestamp.from(Instant.now())); repository.save(order);
與Spring Data 和 Quarkus 一樣,Micronaut將對象持久保存到 PostgreSQL,讓數(shù)據(jù)庫處理 Java 和 PostgreSQL 枚舉類型之間的轉(zhuǎn)換沒有任何問題。
最后,每當(dāng)我們需要從數(shù)據(jù)庫讀回訂單時,我們可以使用以下 JPA API:
PizzaOrder order = repository.findById(id).get();
?System.out.println("Order status: " + order.getStatus());
findById(ID id)方法從數(shù)據(jù)庫檢索記錄并重新創(chuàng)建PizzaOrder實(shí)體,包括enum類型的PizzaOrder.status字段。
總結(jié)
如今,你很可能會在應(yīng)用程序邏輯中使用Java枚舉,并因此需要將它們持久化到一個PostgreSQL數(shù)據(jù)庫。你可以使用特定于ORM的解決方案進(jìn)行Java和PostgreSQL枚舉之間的轉(zhuǎn)換,或者你可以利用基于PostgreSQL的轉(zhuǎn)換操作符的通用方法。
基于轉(zhuǎn)換操作符的方法適用于所有ORM,包括Spring Data、Hibernate、Quarkus和Micronaut,以及受歡迎的PostgreSQL兼容數(shù)據(jù)庫,如Amazon Aurora、Google AlloyDB和YugabyteDB。
以上就是在Java和PostgreSQL枚舉之間轉(zhuǎn)換的通用方法的詳細(xì)內(nèi)容,更多關(guān)于Java和PostgreSQL枚舉轉(zhuǎn)換的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot之RestTemplate在URL中轉(zhuǎn)義字符的問題
這篇文章主要介紹了SpringBoot之RestTemplate在URL中轉(zhuǎn)義字符的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
基于Redis分布式鎖Redisson及SpringBoot集成Redisson
這篇文章主要介紹了基于Redis分布式鎖Redisson及SpringBoot集成Redisson,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小小伙伴可以參考一下2022-09-09
Java?IO篇之Reactor?網(wǎng)絡(luò)模型的概念
Reactor?模式也叫做反應(yīng)器設(shè)計模式,是一種為處理服務(wù)請求并發(fā)提交到一個或者多個服務(wù)處理器的事件設(shè)計模式,Reactor?模式主要由?Reactor?和處理器?Handler?這兩個核心部分組成,本文給大家介紹Java?IO篇之Reactor?網(wǎng)絡(luò)模型的概念,感興趣的朋友一起看看吧2022-01-01
JAVA實(shí)現(xiàn)sm3加密簽名以及防止重復(fù)攻擊
這篇文章主要給大家介紹了關(guān)于JAVA實(shí)現(xiàn)sm3加密簽名以及防止重復(fù)攻擊的相關(guān)資料,SM3是簽名算法,和MD5一樣(對于應(yīng)用層來說),SM4是對稱加密算法,和AES一樣(對于應(yīng)用層來說),需要的朋友可以參考下2023-10-10
Spring?Cache抽象-使用SpEL表達(dá)式解析
這篇文章主要介紹了Spring?Cache抽象-使用SpEL表達(dá)式解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Java?IO流—異常及捕獲異常處理?try…catch…finally
這篇文章主要介紹了Java?IO流—異常及捕獲異常處理?try…catch…finally,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12

