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

news2024/9/24 21:27:36

枚举类型(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.coloradmin.cn/o/1152122.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL 8.2 支持读写分离!

我们一直在等待的 MySQL 读/写分离功能 现在终于可以使用了&#xff01; 在规模上&#xff0c;我们在副本之间分配读取&#xff0c;但这必须在应用程序中以某种方式进行管理&#xff1a;指向在某个地方写入并在其他地方读取。 在 MySQL 8.2 中&#xff0c;MySQL Router 现在能…

SSH 无密登录设置

1 &#xff09; 配置 ssh &#xff08;1&#xff09;基本语法 ssh 另一台电脑的 IP 地址&#xff08;2&#xff09;ssh 连接时出现 Host key verification failed 的解决方法 [libaihadoop102 ~]$ ssh hadoop103 ➢ 如果出现如下内容 Are you sure you want to continue c…

设计模式(20)职责链模式

一、介绍&#xff1a; 1、定义&#xff1a;责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为设计模式&#xff0c;使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链&#xff0c;并沿着…

SpringSecurity6从入门到上天系列第二篇:搭建SpringSecurity6的入门级别程序!

文章目录 前言 1&#xff1a;环境要求 2&#xff1a;技术要求 一&#xff1a;搭建SpringBoot环境 1&#xff1a;创建空项目 2&#xff1a;创建SpringBoot项目 3&#xff1a;编写一个简单的controller 二&#xff1a;整合SpringSecurity 1&#xff1a;引入依赖 2&…

微信小程序设计之目录结构其他文件介绍

一、新建一个项目 首先&#xff0c;下载微信小程序开发工具&#xff0c;具体下载方式可以参考文章《微信小程序开发者工具下载》。 然后&#xff0c;注册小程序账号&#xff0c;具体注册方法&#xff0c;可以参考文章《微信小程序个人账号申请和配置详细教程》。 在得到了测…

Linux基础环境开发工具的使用(yum,vim,gcc,g++)

Linux基础环境开发工具的使用[yum,vim,gcc,g] 一.yum1.yum的快速入门1.yum安装软件2.yum卸载软件 2.yum的生态环境1.操作系统的分化2.四个问题1.服务器是谁提供的呢?2.服务器上的软件是谁提供的呢?3.为什么要提供呢?4.yum是如何得知目标服务器的地址和下载链接呢?5.软件源 …

XML教学视频(黑马程序员精讲 XML 知识!)笔记

第一章XML概述 1.1认识XML XML数据格式&#xff1a; 不是html但又和html有点相似 XML数据格式最主要的功能就是数据传输&#xff08;一个服务器到另一个服务器&#xff0c;一个网站到另一个网站&#xff09;配置文件、储存数据当做小型数据可使用、规范数据格式让数据具有结…

TypeScript深度剖析:TypeScript 中接口的应用场景?

一、是什么 接口是一系列抽象方法的声明&#xff0c;是一些方法特征的集合&#xff0c;这些方法都应该是抽象的&#xff0c;需要由具体的类去实现&#xff0c;然后第三方就可以通过这组抽象方法调用&#xff0c;让具体的类执行具体的方法 简单来讲&#xff0c;一个接口所描述…

PHP自定义文件缓存实现

文件缓存&#xff1a;可以将PHP脚本的执行结果缓存到文件中。当一个PHP脚本被请求时&#xff0c;先查看是否存在缓存文件&#xff0c;如果存在且未过期&#xff0c;则直接读取缓存文件内容返回给客户端&#xff0c;而无需执行脚本 1、文件缓存写法一&#xff0c;每个文件缓存一…

优化改进YOLOv5算法:加入SPD-Conv模块,让小目标无处遁形——(超详细)

1 SPD-Conv模块 论文:https://arxiv.org/pdf/2208.03641v1.pdf 摘要:卷积神经网络(CNNs)在计算即使觉任务中如图像分类和目标检测等取得了显著的成功。然而,当图像分辨率较低或物体较小时,它们的性能会灾难性下降。这是由于现有CNN常见的设计体系结构中有缺陷,即使用卷积…

对PySide6 say Hello(包含环境配置) ——PyQt

前言 一直想学一下python&#xff0c;特别是十一前抢票时达到顶峰。我正好是Qter&#xff0c;所以在网上找了一个教程直接学PyQt。 配置PyQt环境 当前环境 Win10Qt5.15.2 python3.11 之前安装python时好像自动安装了python的包管理工具pip&#xff0c;配置pyqt环境所需要安装…

css:transform实现平移、旋转、缩放、倾斜元素

目录 文档语法示例旋转元素 transform-rotate旋转过渡旋转动画 参考文章 文档 https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform 语法 /* Keyword values */ transform: none;/* Function values */ transform: matrix(1, 2, 3, 4, 5, 6); transform: translate…

c/c++程序的内存开辟时 的内存情况

我们写的代码都是要存放在内存空间中的&#xff0c;我们经常说堆区&#xff0c;静态区&#xff0c;还有栈区&#xff0c;相信很多人不是很明白&#xff0c;在今天这篇博客中让大家对它们有一个粗略的认识 1.栈区&#xff08;static&#xff09; 在执行函数时&#xff0c;函数内…

H5web微信刮一刮效果

H5web微信刮一刮效果 <!DOCTYPE html> <html> <head> <meta charset"utf-8" /> <title>千万别来南宁</title> <meta name"apple-touch-fullscreen" content"yes" /> <meta name"format-detec…

初识Java 16-2 字符串

目录 正则表达式 简单的介绍 正则表达式的创建 量词 Pattern和Matcher 1. find() 2. 分组 3. start()和end() 4. compile()中的标记 5. split() 6. 替换操作 reset() 正则表达式和Java的I/O 本笔记参考自&#xff1a; 《On Java 中文版》 正则表达式 正则表达式是…

【深入浅出汇编语言】寄存器精讲第二期

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、算法模板、汇编语言 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️物理地址二. ⛳️16位结构的CPU三. ⛳️8086CPU给出物理地址的方…

你被骗了吗?别拿低价诱骗机器视觉小白,4000元机器视觉系统怎么来的?机器视觉工程师自己组装一个2000元不到,还带深度学习

淘宝闲鱼&#xff0c;大家搜搜铺价格&#xff0c;特别是机器视觉小白。 机架&#xff1a;&#xff08;新的&#xff09;200元以下。(看需求&#xff0c;自己简单打光&#xff0c;买个50元的。如果复杂&#xff0c;就拿给供应商免费打光) 相机&#xff0c;镜头&#xff1a;&am…

有效的数独

题目链接 有效的数独 题目描述 注意点 board.length 9board[i].length 9board[i][j] 是一位数字&#xff08;1-9&#xff09;或者 ‘.’ 解答思路 首先判断行是否满足数独条件&#xff0c;再判断列是否满足数独条件&#xff0c;最后再判断划分的3x3方格是否满足数独条件…

2.数据结构-链表

概述 目标 链表的存储结构和特点链表的几种分类及各自的存储结构链表和数组的差异刷题(反转链表) 概念及存储结构 先来看一下动态数组 ArrayList 存在哪些弊端 插入&#xff0c;删除时间复杂度高需要一块连续的存储空间&#xff0c;对内存要求比较高&#xff0c;比如要申请…

SSM培训报名管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 培训报名管理系统是一套完善的信息系统&#xff0c;结合SSM框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主 要采用B/S模式开…