如何在Spring JpaRepository中转义问号(?)字符

11

Postgres定义了其他jsonb运算符,如?|

然而,使用Spring JpaRepository查询构建器时,问号字符始终被视为参数,我无法弄清如何转义它(除了在单引号字符串中,但那么查询无效)。

例如:

@Query(value = "SELECT * FROM public.user u WHERE u.authorities ?| array['ROLE_1', 'ROLE_2']", nativeQuery = true)

错误:

java.lang.IllegalArgumentException: Unable to resolve given parameter name [1] to QueryParameter reference
    at org.hibernate.query.internal.QueryParameterBindingsImpl.resolveQueryParameter(QueryParameterBindingsImpl.java:520)
    at org.hibernate.query.internal.QueryParameterBindingsImpl.getQueryParameterListBinding(QueryParameterBindingsImpl.java:498)
    at org.hibernate.query.internal.AbstractProducedQuery.setParameterList(AbstractProducedQuery.java:560)

有没有一种方法可以避免这个问题,或者有不同的解决方案来使用包含 ? 字符的Postgres原生操作符。目前尝试使用 ??| 或 \?| 进行转义无效。注意:我还尝试使用自定义方言函数,但出现了相同的问题。
库:
  • hibernate 5.2.16
  • hibernate-jpa 2.1
  • spring-data-jpa 2.0.6.RELEASE
  • postgresql 42.2.2

感谢大家的回复!

2
(解决方案1)尝试再添加一个?。我认为这应该可以解决问题。所以将??|作为运算符。它在JDBC中有效,所以我不确定它是否适用于JPA。(解决方案2)Execute是原始SQL,而不是准备好的语句。(解决方案3)为此操作符在PL/pgSQL中创建一个包装函数。但要注意,它需要功能索引才能在大量数据上快速工作。 - Kamil Gosciminski
1
任何最近的JDBC驱动程序都允许您使用两个引号代替一个问号,例如使用??|代替?| - user330315
@ŁukaszKamiński,您能分享一下您的经验吗? - Cepr0
1
@Cepr0 你的意思是将其作为答案发布吗?我认为转义问号更容易维护和使用。但总体而言,可以在此处找到创建语法和运算符定义:select * from pg_operator where oprname = '?|' and oprleft = (select oid from pg_type where typname = 'jsonb');。更容易的方法是使用pgAdmin或其他工具,在浏览现有运算符(在pg_catalog中)时显示创建语句。 - Łukasz Kamiński
@ŁukaszKamiński 预先感谢您! - Cepr0
显示剩余8条评论
3个回答

10
你可以直接调用函数jsonb_exists_any()。在你的情况下,它将是: jsonb_exists_any(u.authorities::jsonb, array['ROLE_1', 'ROLE_2'])

2
请注意,大多数jsonb函数即使相应的运算符使用索引也不会利用索引! - lathspell

8

如果无法转义 ?,可以创建具有不同名称的重复运算符。

新运算符

在Postgres中创建运算符的语法:

CREATE OPERATOR name (
    PROCEDURE = function_name
    [, LEFTARG = left_type ] [, RIGHTARG = right_type ]
    [, COMMUTATOR = com_op ] [, NEGATOR = neg_op ]
    [, RESTRICT = res_proc ] [, JOIN = join_proc ]
    [, HASHES ] [, MERGES ]
)

如果在 jsonb 中使用 ?|,它将是:

CREATE OPERATOR ^|(
  PROCEDURE = jsonb_exists_any,
  LEFTARG = jsonb,
  RIGHTARG = _text,
  RESTRICT = contsel,
  JOIN = contjoinsel);

我已经使用^|作为一个例子,也可以是列表中的任何一个序列:+ - * / < > = ~ ! @ # % ^ & | ?`。你可以通过查询pg_catalog.pg_operator表来找到你感兴趣的运算符的当前定义。
SELECT oid, *
  FROM pg_catalog.pg_operator
 WHERE oprname = '?|'
   AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'jsonb');

您还可以使用GUI工具,如pgAdmin,并浏览pg_catalog,以获取可重用的SQL定义。

启用索引

如果您要为此“新”运算符使用索引,则需要创建新的运算符类和可选系列。在我们的情况下,我们需要两者,因为我们无法将其添加到现有系列中,因为默认运算符已经占用了strategy slot

就像操作符一样,建议使用GUI工具,如pgAdmin,浏览运算符类并复制&粘贴它。

首先,我们获取我们复制的运算符的OID:

SELECT oid, *
  FROM pg_catalog.pg_operator
 WHERE oprname = '?|'
   AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'jsonb');

同样适用于运算符族(我们将从operator class table中获取它),我们正在寻找gin类,因为这是支持?|的类。使用opcdefault,因为有一个不支持此运算符的可选类jsonb_path_ops
SELECT opcfamily
  FROM pg_opclass
 WHERE opcintype = (SELECT oid FROM pg_type WHERE typname = 'jsonb')
   AND opcmethod = (SELECT oid FROM pg_am WHERE amname = 'gin')
   AND opcdefault

然后我们得到了运营商使用的策略,我们进行了复制:

SELECT amopstrategy,
       (SELECT typname FROM pg_type WHERE oid = amoplefttype) AS left_t, 
       (SELECT typname FROM pg_type WHERE oid = amoprighttype) AS right_t,*
FROM pg_amop
WHERE amopfamily = 4036 --family oid
  AND amopopr = 3248 --operator oid

接下来是 类使用的函数

SELECT amprocnum, amproc::text, pg_get_function_identity_arguments(amproc::oid) AS args,
      (SELECT typname FROM pg_type WHERE oid = amproclefttype) AS left_t,
      (SELECT typname FROM pg_type WHERE oid = amprocrighttype) AS right_t,*
FROM pg_amproc
WHERE amprocfamily = 4036 --op family

这让我们来到了这个操作员类。如果不存在,它将创建操作员家族。
CREATE OPERATOR CLASS jsonb_ops_custom
   FOR TYPE jsonb USING gin AS
   OPERATOR 10  ^|(jsonb, _text),
   FUNCTION 1  gin_compare_jsonb(text, text),
   FUNCTION 2  gin_extract_jsonb(jsonb, internal, internal),
   FUNCTION 3  gin_extract_jsonb_query(jsonb, internal, smallint, internal, internal, internal, internal),
   FUNCTION 4  gin_consistent_jsonb(internal, smallint, jsonb, integer, internal, internal, internal, internal),
   FUNCTION 6  gin_triconsistent_jsonb(internal, smallint, jsonb, integer, internal, internal, internal);

现在您只需要使用已创建的运算符名称创建索引,类似于以下内容:
CREATE INDEX ON jsonb_table USING gin(jsonb_column jsonb_ops_custom)

而且你应该能够使用索引:

SET enable_seqscan = off;
EXPLAIN ANALYZE
SELECT * FROM jsonb_table WHERE jsonb_column ^| array['b', 'c'];

在我的Postgres(13.2)中,我发现对于?|oprrest = matchingseloprjoin = matchingjoinsel,而对于^|,您有oprrest = contseloprjoin = contjoinsel。这是故意的偏离,还是您运行的Postgres版本中?|操作符的字段不同? - ghallas

1
作为针对特定情况的解决方法,我创建了一个自定义运算符:
CREATE OPERATOR ~~~| (
    LEFTARG = jsonb,
    RIGHTARG = _text,
    PROCEDURE = pg_catalog.jsonb_exists_any
)

然后在我的查询中:WHERE u.authorities ~~~| array['ROLE_1', 'ROLE_2']

@ŁukaszKamiński在他的答案https://dev59.com/dlUL5IYBdhLWcg3wWG9I#50488457中详细介绍了这个解决方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接