sql - Postgres Spring Data JPA 将列表作为值传递

标签 sql postgresql hibernate spring-data-jpa spring-data

我有一个表,其中的 name 列对我来说很重要。

insert into tab (name) values ('a');
insert into tab (name) values ('b');
insert into tab (name) values ('c');
insert into tab (name) values ('d');

从 Hibernate 5 支持的 Spring Data Repository 中,我想传递一个名称列表,并返回传入的表中不存在的名称。因此,对于列表 ('a', 'b ', 'c', 'baz') 它应该返回值为 baz 的单行。

如果手工编写,以下查询将适用于这种情况。

WITH list(name) AS (VALUES ('a'), ('b'), ('c'), ('baz'))
SELECT name
FROM list
    EXCEPT
SELECT name
FROM tab
WHERE name IN (SELECT name FROM list)

因此,我将以下方法添加到我的 JpaRepository 中。

@Query(value = "WITH list(name) "
    + "    AS (VALUES :names) "
    + "SELECT name "
    + "FROM list "
    + "    EXCEPT "
    + "SELECT name "
    + "FROM tab "
    + "WHERE name IN (SELECT name FROM list)", nativeQuery = true)
List<String> findNamessMissingFromGivenList(Collection<String> names);

但是,Spring Data 传入 :name 集合的方式是放置 ('a', 'b', 'c', 'baz') ('a'), ('b'), ('c'), ('baz')。因此,集合非常适合 IN 子句,但不适用于这种情况。本质上,我需要传入列表并将它们放入伪表中,就像 VALUES 所做的那样。

我不想手动构建任何查询并避免 SQL 注入(inject)。有没有办法修改此查询以接受来自 JPA 的集合?

最佳答案

不幸的是 VALUES :names 不会产生您需要的 VALUES (?),(?),(?),(?) ,而是会产生 值(?,?,?,?)。您需要一个返回一组所需类型的函数。要获取您的数据库版本的列表,您可以使用以下查询:

SELECT 
  p.proname as "Name",
  pg_catalog.pg_get_function_result(p.oid) as "Result data type",
  pg_catalog.pg_get_function_arguments(p.oid) as "Argument data types"
FROM pg_catalog.pg_proc p
WHERE pg_catalog.pg_get_function_result(p.oid) LIKE 'SETOF %';

由于您寻求的是文本类型,因此可行的函数(至少在 postgres 9.5 中)是:

  • unnest(anyarray) 返回任意元素的集合
  • regexp_split_to_table(目标文本、模式文本、标志文本) 返回一组文本
  • json_object_keys(json) 返回一组文本
  • json_object_keys(jsonb) 返回一组文本

第一个选项需要支持将数组作为参数传递。您可以通过添加 com.mopano:hibernate-array-contributor 来完成此操作库到您的项目,或将字符串编译到 postgresql array-as-string syntax并使用类型转换,从而使您的子查询:

WITH list(name) AS (select unnest(cast('{a,b,c,baz}' as text array)))

避免使用 ::text[] 语法进行类型转换,因为 Hibernate 会将其解析为输入参数并将其替换为问号。有些人尝试使用数组构造函数,例如 ARRAY[ :values ],但这仅在您将每个值直接输入到查询中而不是通过 JDBC 绑定(bind)它们时才有效。

第二个要求您首先将参数编译为一个字符串,其中包含任何一个参数中都不存在的分隔符,然后将该分隔符用作正则表达式,第三个参数必须是'g' 以确保它拆分所有匹配项,而不是第一个匹配项。虽然这很困难并且容易出错,但无需额外的库即可完成。

第三个选项是编译一个 json 对象,其中您的值是键,并且您可能还需要一个额外的库来支持 json 参数。我已经发布了其中的两篇文章,但这是一个可以通过更简单的方式实现的解决方案。

最后,无论您选择哪一个,查询中的 where 条件也是向后的。最终结果(假设您使用数组作为字符串选项并且没有外部库)应如下所示(在本地测试):

WITH list(name) AS (select unnest(cast('{a,b,c,baz}' as text array)))
SELECT name
FROM list WHERE name NOT IN (SELECT name FROM tab);
 name 
------
 baz
(1 row)

关于sql - Postgres Spring Data JPA 将列表作为值传递,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64495265/

相关文章:

python - 如何在没有任何顺序的情况下更新我的数据库对象?

python - Django - 具有 order_with_respect_to 属性的 bulk_create 对象

PostgreSQL 没有使用直接索引

java - 使用 Hibernate 检索特定记录

java - 使用 JPA 避免结果列表中的重复项

java - JDBC COPY 与 Ant

mysql - 如何在票证支持系统中获取单张票证的所有评论?

sql - 在 SQL Server 中将 VarChar 转换为 VarBinary 时出错

sql - 如何将 Oracle apex 项目限制为数字/字符值

php - ORDER BY 子句中的子查询 MySQL