在Java和PostgreSQL枚举之间进行转换的通用方法

枚举类型(enum)是一种方便的数据类型,允许我们指定一个常量列表,对象字段或数据库列可以设置为该列表中的值。

枚举的美妙之处在于我们可以通过提供人类可读格式的枚举常量来确保数据完整性。因此,Java和PostgreSQL原生支持这种数据类型并不令人惊讶。

但是,Java和PostgreSQL枚举之间的转换并不是开箱即用的。JDBC API不将枚举识别为一个独特的数据类型,这使得如何处理转换的决策留给了JDBC驱动程序。通常,驱动程序对此无能为力 —— 这是一个先有鸡还是先有蛋的问题。

有许多解决方案可以帮助您在Java和PostgreSQL枚举之间进行映射,但大多数解决方案都是ORM或JDBC特定的。这意味着适用于Spring Data的建议可能不适用于Quarkus,反之亦然。

在本文中,我将回顾处理Java和PostgreSQL枚举转换的通用方法。此方法适用于普通的JDBC API和流行的ORM框架,如Spring Data、Hibernate、Quarkus和Micronaut。此外,它还被构建在PostgreSQL上的数据库支持,包括Amazon Aurora、Google AlloyDB和YugabyteDB。

创建Java实体对象和枚举 

假设我们有一个代表披萨订单的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}

当我们在线订购披萨时,应用程序将状态设置为Ordered。一旦厨师开始处理我们的订单,状态就会变为Baking。一旦披萨刚出炉,就会有人取走并送到我们的门口 - 状态随后更新为Delivering。最后,状态被设置为YummyInMyTummy,意味着我们享受了披萨(希望如此!)

创建数据库表和枚举 

为了在PostgreSQL中持久化披萨订单,让我们创建以下与我们的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 对应的常量(状态)类似。

解决转换问题

如果我们使用psql(或其他SQL工具)连接到PostgreSQL并执行以下INSERT语句,它将成功执行:

insert into pizza_order (id, status, order_time) values (1, 'Ordered', now());

该语句很好地接受文本表示形式的订单状态(枚举数据类型)Ordered。

看到这一点后,我们可能会想以这种格式将 Java 枚举值发送到 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();

但是,该语句将出现以下异常:

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 会话直接执行语句时接受枚举文本表示,但它不支持varchar(由 Java 传递)和我们的枚举类型之间的转换。

对于普通 JDBC API 修复此问题的一种方法是将 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,则需要寻找其他解决方案。

数据库级别的类型转换

数据库提供了通用的解决方案。

PostgreSQL 支持强制转换运算符,可以自动执行两种数据类型之间的转换。

因此,在我们的例子中,我们需要做的就是创建以下运算符:

CREATE CAST (varchar AS order_status) WITH INOUT AS IMPLICIT;

创建的运算符将在varchar类型(由 JDBC 驱动程序传递)和我们的数据库级order_status枚举类型之间进行映射。该WITH INOUT AS IMPLICIT子句确保对于使用该order_status类型的所有语句,强制转换将透明且自动地发生。

使用纯 JDBC API 进行测试

在 PostgreSQL 中创建该强制转换运算符后,早期的 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,驱动程序会将其以表示形式发送到 PostgreSQL varchar,该表示形式会自动将该varchar值转换为order_status类型。

如果您从数据库读回订单,那么您可以轻松地从一个值重建 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 验证基于强制转换运算符的方法。如今,您可能会直接使用 ORM 而不是 JDBC API。

首先,我们需要PizzaOrder用一些 JPA 和 Hibernate 注释来标记我们的实体类:

@Entitypublic 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 实现(通常是 Hibernate)将枚举值作为 a 传递String给驱动程序。

其次,我们PizzaOrderRepository使用 Spring Data API 创建并保存实体对象:

// The repository interfacepublic interface PizzaOrderRepository extends JpaRepository<PizzaOrder, Integer> {}// The service class@Servicepublic 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)我们的源代码中的某个位置调用该方法时,订单将被创建并成功保存到数据库中。Java 和 PostgreSQL 枚举之间的转换不会出现任何问题。

最后,如果我们需要从数据库读回订单,我们可以使用该JpaRepository.findById(ID id)方法,该方法根据其表示重新创建 Java 枚举String:

PizzaOrder order = repo.findById(orderId).get();System.out.println("Order status: " + order.getStatus());

使用 Quarkus 进行测试

Quarkus 怎么样,它可能是您的#1 ORM?只要 Quarkus 支持 Hibernate 作为 JPA 实现,与 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实例进行数据库请求:

@ApplicationScopedpublic 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);

当我们调用pizzaService.addNewOrder(1)应用程序逻辑中的某处时,Quarkus 将成功保留顺序,并且 PostgreSQL 将负责 Java 和 PostgreSQL 枚举转换。

最后,要从数据库读回订单,我们可以使用以下方法EntityManager将结果集中的数据映射到PizzaOrder实体类(包括枚举字段):

PizzaOrder order = entityManager.find(PizzaOrder.class, 1);System.out.println("Order status: " + order.getStatus());  

使用 Micronaut 进行测试

好吧,好吧, Micronaut怎么样?我喜欢这个框架,你可能也会喜欢它。

数据库端强制转换运算符对于 Micronaut 来说也是一个完美的解决方案。为了让事情有所不同,我们不会为 Micronaut 使用 Hibernate。相反,我们将通过使用该模块来依赖 Micronaut 自己的功能micronaut-data-jdbc:

<dependency>    <groupId>io.micronaut.data</groupId>    <artifactId>micronaut-data-jdbc</artifactId></dependency>// other dependencies

首先,我们对PizzaOrder实体进行注释:

@MappedEntitypublic class PizzaOrder {    @Id    private Integer id;    @Enumerated(EnumType.STRING)    private OrderStatus status;    private Timestamp orderTime;   // getters and setters are omitted}

接下来,定义PizzaRepository:

@JdbcRepository(dialect = Dialect.POSTGRES)public interface PizzaRepository extends CrudRepository<PizzaOrder, Integer> {}

然后,通过在应用程序逻辑中的某个位置调用以下代码片段,将披萨订单存储在数据库中:

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,让数据库处理 Java 和 PostgreSQL 枚举类型之间的转换没有任何问题。

最后,每当我们需要从数据库读回订单时,我们可以使用以下 JPA API:

PizzaOrder order = repository.findById(id).get();System.out.println("Order status: " + order.getStatus());

findById(ID id)方法从数据库检索记录并重新创建PizzaOrder实体,包括enum类型的PizzaOrder.status字段。

总结 

如今,你很可能会在应用程序逻辑中使用Java枚举,并因此需要将它们持久化到一个PostgreSQL数据库。你可以使用特定于ORM的解决方案进行Java和PostgreSQL枚举之间的转换,或者你可以利用基于PostgreSQL的转换操作符的通用方法。

基于转换操作符的方法适用于所有ORM,包括Spring Data、Hibernate、Quarkus和Micronaut,以及受欢迎的PostgreSQL兼容数据库,如Amazon Aurora、Google AlloyDB和YugabyteDB。

作者:Denis Magda

更多内容请关注公号【云原生数据库

squids.cn,云数据库RDS,迁移工具DBMotion,云备份DBTwin等数据库生态工具。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/156561.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5.4 完整性约束命名子句

思维导图: 笔记&#xff1a;5.4 完整性约束命名子句 定义: 完整性约束是在CREATE TABLE语句中定义的。SQL为CREATE TABLE语句提供了CONSTRAINT子句&#xff0c;用于对完整性约束进行命名。命名的目的是方便增加或删除约束。 基本结构: CONSTRAINT <完整性约束名称> &l…

Flutter的The file name ‘xxxx.dart‘ isn‘t a snake_case identifier警告

文章目录 警告原因分析解决方法dart的一些命名规则变量和函数命名&#xff1a;类和类型命名&#xff1a;常量和枚举&#xff1a;文件命名&#xff1a;包命名&#xff1a;注释&#xff1a;命名一致性&#xff1a;避免缩写&#xff1a;可搜索的命名&#xff1a; 一些好习惯 警告 …

AI“走深向实”,蚂蚁蚁盾在云栖大会发布实体产业「知识交互建模引擎」

数字化起步晚、数据分散稀疏、专业壁垒高、行业知识依赖「老师傅」&#xff0c;是很多传统产业智能化发展面临的难题。2023年云栖大会上&#xff0c;蚂蚁集团安全科技品牌蚁盾发布“知识交互建模引擎”&#xff0c;将实体产业知识与AI模型有机结合&#xff0c;助力企业最快10分…

Redis之 redis.config配置文件

文章目录 前言一、基本配置1.单位2.包含3.网络配置4.通用5.快照6.安全7.限制8.仅追加模式 二、总体主要介绍总结 前言 行家一出手&#xff0c;就知有没有&#xff0c;让一起学习redis.config配置文件。 一、基本配置 Redis 的配置文件位于 Redis 安装目录下&#xff0c;文件名…

表白墙/留言墙 —— 初级SpringBoot项目,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信你对这篇博客也感兴趣o (ˉ▽ˉ&#xff1b;) 用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证&#xff08;带前后端源码&#xff09;全方位全流程超详细教程 目录 项目前端页面展…

pytorch复现3_GoogLenet

背景&#xff1a; GoogLeNeta是2014年提出的一种全新的深度学习结构&#xff0c;在这之前的AlexNet、VGG等结构都是通过增大网络的深度(层数)来获得更好的训练效果&#xff0c;但层数的增加会带来很多负作用&#xff0c;比如overfit、梯度消失、梯度爆炸等。GoogLeNet通过引入i…

Centos7下生成https自签名证书

1、安装openssl yum install openssl2、生成带密码的私有秘钥文件 openssl genrsa -des3 -out server.key 2048使用带密码的私有秘钥文件时需要输入密码&#xff0c;这里直接输入&#xff1a;123456 3、生成不带密码的私有秘钥文件 openssl rsa -in server.key -out serve…

自己动手实现一个深度学习算法——二、神经网络的实现

文章目录 1. 神经网络概述1&#xff09;表示2&#xff09;激活函数3&#xff09;sigmoid函数4&#xff09;阶跃函数的实现5&#xff09;sigmoid函数的实现6)sigmoid函数和阶跃函数的比较7&#xff09;非线性函数8&#xff09;ReLU函数 2.三层神经网络的实现1&#xff09;结构2&…

“优等生”宁波银行公布三季报:营利齐升,风控抵补能力持续夯实

撰稿 | 多客 来源 | 贝多财经 10月30日晚&#xff0c;城商行“优等生”宁波银行披露2023年三季报&#xff0c;在效益与规模稳步提升的同时&#xff0c;资产质量也保持了优异水平。 前三季度&#xff0c;宁波银行实现归属于母公司股东的净利润193.49亿元&#xff0c;同比增长1…

智慧公厕:科技赋予公共卫生新生命,提升城市管理品质

在现代化城市中&#xff0c;公共卫生设施的发展与提升一直是对城市管理者和市民的共同期望。然而&#xff0c;传统的公共厕所常常令人困扰&#xff0c;脏乱臭成为难题。为了解决这一难题&#xff0c;广州中期科技科技有限公司全新升级的智慧公厕整体解决方案&#xff0c;补誉为…

数模竞赛那么累,究竟能给我带来什么?

国赛官网上有这么一句话&#xff1a;一次参赛&#xff0c;终生受益。 学生时代&#xff0c;我对这句话没啥感触。 因为刚开始学数模时感觉很没头绪&#xff0c;书也看不懂&#xff0c;论文也看不懂&#xff0c;看啥都看不懂。 比赛时题目看不懂&#xff0c;答案搜不到&#xf…

历年网规上午真题笔记(2016年)

解析:B 嵌入式系统的存储结构采用分级的方法设计,即寄存器组、高速缓冲存储器(Cache)、内存(包括Flash)和外存,它们在存取速度上依次递减,而在存储容量上逐级增加。 解析:C/A ERP是一种面向制作行业进行物质资源、资金资源和信息资源集成一体化管理的企业信息管理系…