如何使用 Spring JpaRepository 转义问号 (?) 字符

How to escape question mark (?) character with Spring JpaRepository

Postgres 定义 additional jsonb Operators 例如 ?|.

但是,使用 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 本机运算符。

试图用 ??| 逃避它或\?|目前不工作。

注意:我也试过使用自定义方言功能,但它以同样的问题结束。

图书馆:

感谢大家的回复!

作为该特定案例的解决方法,我创建了一个自定义运算符:

CREATE OPERATOR ~~~| (
    LEFTARG = jsonb,
    RIGHTARG = _text,
    PROCEDURE = pg_catalog.jsonb_exists_any
)

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

@ŁukaszKamiński 在他的回答中详细说明了这个解决方法:

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

新运营商

Postgres 中 creating operators 的语法:

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 table.

找到您感兴趣的运算符的当前定义
SELECT oid, *
  FROM pg_catalog.pg_operator
 WHERE oprname = '?|'
   AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'jsonb');

您还可以使用 pgAdmin 等 GUI 工具并浏览 pg_catalog 以获得 SQL 定义以供重用。

启用索引

如果要为此 "new" 运算符使用索引,您将需要创建新的运算符 class 和可选的系列。在我们的例子中,我们需要两者,因为我们不能将它添加到现有系列中,因为默认运算符已经采用 strategy slot.

就像运算符一样,建议使用 pgAdmin 等 GUI 工具浏览运算符 classes 并复制粘贴即可。

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

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

运算符家族也是如此(我们将从 operator class table 获取它),我们正在寻找 gin class 因为这是支持 ?| 的那个。使用opcdefault,因为有可选的class 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

然后我们得到 strategy used by operator 我们复制了:

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

然后 functions used by class:

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

这让我们想到了 operator class。如果运算符家族不存在,它将创建它。

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'];

您可以直接调用函数 jsonb_exists_any()。 所以在你的情况下它将是

jsonb_exists_any(u.authorities::jsonb, array['ROLE_1', 'ROLE_2'])