在Postgres和Hibernate 6中修改JSON数据

这是关于在使用Hibernate框架(版本6.)的项目中支持Postgres JSON函数的系列文章中的另一篇文章,本文的主题是对JSON记录的修改操作。与前一篇文章一样,值得一提的是,Postgres 现在可能不像其他 NoSQL 数据库(如 MongoDB)那样拥有对 JSON 进行修改的全面操作(不过,通过适当的函数构造,也可以达到同样的效果)。它仍然适合大多数需要修改 JSON 的项目。此外,Postgres 支持事务(NoSQL 数据库不支持这种级别的事务),因此使用 Postgres 处理 JSON 数据是个不错的主意。当然,NoSQL 数据库还有其他优点,可能适合更好的项目。

关于 Postgres 对 JSON的支持,一般有很多文章。本文重点介绍如何将该支持与 Hibernate 6 库集成。

如果有人对使用 Postgres 和 Hibernate 查询 JSON 数据或进行文本搜索感兴趣,请参阅以下链接:

测试数据

假设我们的数据库有一个名为item的表,其中有一个JSON内容的列,如下面的示例所示:

SQL

 

我们可能还有一些测试数据:

SQL

 

原生SQL执行

像其他Java框架一样,使用Hibernate,您可以运行原生SQL查询 – 这在互联网上有很多文档和示例。这就是为什么在这篇文章中,我们不会重点讨论原生SQL操作执行。然而,将会有JPA操作生成的SQL示例。因为Hibernate是JPA的一个实现,所以展示JPA API如何修改Postgres数据库中的JSON数据是有意义的。

修改JSON对象属性而非整个JSON对象(路径)

为单个列设置整个JSON载荷是容易的,并且不需要太多解释。我们只需在代表JSON内容的列的Entity类中设置属性值。

对于单个数据库行,设置JSON的单个或多个属性也是类似的。我们只需读取表行,将JSON值反序列化为表示JSON对象的POJO,为特定属性设置值,并使用整个载荷更新数据库记录。然而,当想要修改多个数据库行的JSON属性时,这种方法可能并不实用。

假设我们必须对特定的JSON属性进行批量更新。从数据库中获取并更新每个记录可能不是有效的方法。

使用一个update语句进行此类更新会更好,在该语句中我们为特定的JSON属性设置值。幸运的是,Postgres有修改JSON内容的函数,并可以在SQL更新语句中使用。

Posjsonhelper

Hibernate在版本7中更好地支持JSON修改,包括本文中提到的大多数函数和运算符。然而,在版本6中没有计划添加此类支持。幸运的是,Posjsonhelper项目为Hibernate版本6添加了此类支持。下面所有的示例都将使用Posjsonhelper库。查看这个链接了解如何将库附加到您的Java项目中。您还需要附加FunctionContributor

所有示例都使用代表上述item表的Java实体类:

Java

 

jsonb_set 函数包装器

当需要修改JSON数据时,jsonb_set函数可能是最有帮助的函数。它允许根据数组索引设置JSON对象的具体属性和特定数组元素。

例如,下面的代码向内部属性"child"添加了属性"birthday"

Java

 

此代码将生成如下SQL语句:

SQL

 

连接运算符(||)包装器

连接运算符(||的包装器将两个JSONB值连接成一个新的JSONB值。

根据Postgres文档,该运算符的行为如下:

连接两个数组会生成包含每个输入所有元素的数组。连接两个对象会生成一个包含它们键的并集的对象,当有重复的键时,采用第二个对象的值。在其他所有情况下,都将非数组输入转换为单元素数组,然后像处理两个数组一样进行操作。不递归操作:只合并顶层数组或对象结构。

以下是在代码中使用此包装器的示例:

Java

 

代码将与数据库中已存储的child属性合并的JSON对象合并。

此代码生成的SQL查询如下:

SQL

 

基于指定路径删除字段或数组元素(#-)

Posjsonhelper 有一个用于删除操作的包装器(#-)。它根据指定路径删除字段或数组元素,其中路径元素可以是字段键或数组索引。例如,下面的代码根据 "child.pets" JSON 路径从 JSON 对象属性中删除。

Java

 

生成的 SQL 将是:

SQL

 

在指定路径删除多个数组元素

默认情况下,Postgres(至少在版本 16 中)没有内置函数允许根据它们的值删除数组元素。然而,它确实有内置运算符 -#,我们上面提到过,它可以帮助根据索引删除数组元素,而不是它们的值。

为此目的,Posjsonhelper 可以生成一个必须添加到 DDL 操作并在您的数据库上执行的函数。

SQL

 

其中一个包装器将使用这个函数允许从 JSON 数组中删除多个值。这段代码删除了 "child.inventory" 属性中的 "mask""compass" 元素。

Java

 

以下是上述代码生成的 SQL:

SQL

 

Hibernate6JsonUpdateStatementBuilder: 如何用一个更新语句组合多个修改操作

上述所有示例都演示了单个操作,该操作修改JSON数据。当然,我们的代码中可以有多个更新语句,这些更新语句一起使用本文中提到的包装器。然而,了解这些操作和函数如何执行是非常重要的,因为当第一个JSON操作的结果是后续JSON修改操作的输入时,这才有意义。该操作的输出将是下一个操作的输入,依此类推,直到最后的JSON修改操作。

为了更好地说明这一点,请查看SQL代码。

SQL

 

这假设我们有四个jsonb_set 函数的执行和两个delete操作。最嵌套的delete操作是第一个JSON修改操作,因为存储JSON数据的列的原始值被作为参数传递。

虽然这是正确的做法,现有的包装器允许创建这样的UPDATE语句,但从代码角度来看,这可能不容易阅读。幸运的是,Posjsonhelper有一个构建器组件,使构建这样的复杂语句变得容易。

Hibernate6JsonUpdateStatementBuilder类型允许构建包含多个修改JSON的操作的更新语句,并且相互依赖。

以下是一个代码示例:

Java

 

前面提到的SQL语句就是由这段代码生成的。

想了解更多关于构建器如何工作的信息,请查看文档

结论

Postgres数据库在JSON数据修改操作方面具有广泛的可能性。这使我们对Postgres作为文档存储解决方案的选择持乐观态度。所以,如果我们的事业不需要更高的读取性能、更好的扩展性或分片(尽管所有这些都可以通过Postgres数据库实现,尤其是通过像AWS这样的云提供商提供的解决方案),那么考虑将JSON文档存储在Postgres数据库中是有价值的——更不用说像Postgres这样的数据库支持事务了。

Source:
https://dzone.com/articles/modify-json-data-in-postgres-and-hibernate