在Java和PostgreSQL枚舉之間轉(zhuǎn)換的通用方法
枚舉類型(enum)是一種方便的數(shù)據(jù)類型,允許我們指定一個常量列表,對象字段或數(shù)據(jù)庫列可以設(shè)置為該列表中的值。
枚舉的美妙之處在于我們可以通過提供人類可讀格式的枚舉常量來確保數(shù)據(jù)完整性。因此,Java和PostgreSQL原生支持這種數(shù)據(jù)類型并不令人驚訝。
但是,Java和PostgreSQL枚舉之間的轉(zhuǎn)換并不是開箱即用的。JDBC API不將枚舉識別為一個獨特的數(shù)據(jù)類型,這使得如何處理轉(zhuǎn)換的決策留給了JDBC驅(qū)動程序。通常,驅(qū)動程序?qū)Υ藷o能為力 —— 這是一個先有雞還是先有蛋的問題。
有許多解決方案可以幫助您在Java和PostgreSQL枚舉之間進行映射,但大多數(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è)我們有一個代表披薩訂單的Java實體對象:
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 }
當我們在線訂購披薩時,應(yīng)用程序?qū)顟B(tài)設(shè)置為Ordered。一旦廚師開始處理我們的訂單,狀態(tài)就會變?yōu)锽aking。一旦披薩剛出爐,就會有人取走并送到我們的門口 - 狀態(tài)隨后更新為Delivering。最后,狀態(tài)被設(shè)置為YummyInMyTummy,意味著我們享受了披薩(希望如此?。?/p>
創(chuàng)建數(shù)據(jù)庫表和枚舉
為了在PostgreSQL中持久化披薩訂單,讓我們創(chuàng)建以下與我們的PizzaOrder實體類映射的表:
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。
看到這一點后,我們可能會想以這種格式將 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 支持強制轉(zhuǎn)換運算符,可以自動執(zhí)行兩種數(shù)據(jù)類型之間的轉(zhuǎn)換。
因此,在我們的例子中,我們需要做的就是創(chuàng)建以下運算符:
CREATE CAST (varchar AS order_status) WITH INOUT AS IMPLICIT;
創(chuàng)建的運算符將在varchar類型(由 JDBC 驅(qū)動程序傳遞)和我們的數(shù)據(jù)庫級order_status枚舉類型之間進行映射。該WITH INOUT AS IMPLICIT子句確保對于使用該order_status類型的所有語句,強制轉(zhuǎn)換將透明且自動地發(fā)生。
使用純 JDBC API 進行測試
在 PostgreSQL 中創(chuàng)建該強制轉(zhuǎ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 進行測試
接下來,讓我們使用 Spring Data 驗證基于強制轉(zhuǎn)換運算符的方法。如今,您可能會直接使用 ORM 而不是 JDBC API。
首先,我們需要PizzaOrder用一些 JPA 和 Hibernate 注釋來標記我們的實體類:
@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 實現(xiàn)(通常是 Hibernate)將枚舉值作為 a 傳遞String給驅(qū)動程序。
其次,我們PizzaOrderRepository使用 Spring Data API 創(chuàng)建并保存實體對象:
// 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); }
當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 進行測試
Quarkus 怎么樣,它可能是您的#1 ORM?只要 Quarkus 支持 Hibernate 作為 JPA 實現(xiàn),與 Spring Data 就沒有顯著差異。
首先,我們PizzaOrder用 JPA 和 Hibernate 注解來注解我們的實體類:
@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ù)庫請求:
@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);
當我們調(diào)用pizzaService.addNewOrder(1)應(yīng)用程序邏輯中的某處時,Quarkus 將成功保留順序,并且 PostgreSQL 將負責 Java 和 PostgreSQL 枚舉轉(zhuǎn)換。
最后,要從數(shù)據(jù)庫讀回訂單,我們可以使用以下方法EntityManager將結(jié)果集中的數(shù)據(jù)映射到PizzaOrder實體類(包括枚舉字段):
PizzaOrder order = entityManager.find(PizzaOrder.class, 1); ?System.out.println("Order status: " + order.getStatus()); ?
使用 Micronaut 進行測試
好吧,好吧, Micronaut怎么樣?我喜歡這個框架,你可能也會喜歡它。
數(shù)據(jù)庫端強制轉(zhuǎn)換運算符對于 Micronaut 來說也是一個完美的解決方案。為了讓事情有所不同,我們不會為 Micronaut 使用 Hibernate。相反,我們將通過使用該模塊來依賴 Micronaut 自己的功能micronaut-data-jdbc:
<dependency> <groupId>io.micronaut.data</groupId> <artifactId>micronaut-data-jdbc</artifactId> </dependency> // other dependencies
首先,我們對PizzaOrder實體進行注釋:
@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)換沒有任何問題。
最后,每當我們需要從數(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實體,包括enum類型的PizzaOrder.status字段。
總結(jié)
如今,你很可能會在應(yīng)用程序邏輯中使用Java枚舉,并因此需要將它們持久化到一個PostgreSQL數(shù)據(jù)庫。你可以使用特定于ORM的解決方案進行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)換的通用方法的詳細內(nèi)容,更多關(guān)于Java和PostgreSQL枚舉轉(zhuǎn)換的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java 實現(xiàn)簡易教務(wù)管理系統(tǒng)的代碼
這篇文章主要介紹了Java 實現(xiàn)簡易教務(wù)管理系統(tǒng)的代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07IDEA使用技巧之如何將本地項目和git遠程項目關(guān)聯(lián)
這篇文章主要介紹了IDEA使用技巧之如何將本地項目和git遠程項目關(guān)聯(lián)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02淺談@FeignClient中name和value屬性的區(qū)別
這篇文章主要介紹了@FeignClient中name和value屬性的區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07淺談springboot多模塊(modules)開發(fā)
這篇文章主要介紹了淺談springboot多模塊(modules)開發(fā),詳細的介紹了springboot多模塊的實現(xiàn),有興趣的可以了解一下2017-09-09java獲取request中的參數(shù)以及java解析URL問號后的參數(shù)
這篇文章主要介紹了java獲取request中的參數(shù)以及java解析URL問號后的參數(shù)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12