将 null 绑定到准备好的语句时出现 Postgres bytea 错误

Postgres bytea error when binding null to prepared statements

我正在使用一个使用 JPA 和 Postgres 数据库的 Java 应用程序,我正在尝试创建一个灵活的准备好的语句,它可以处理可变数量的输入参数。一个示例查询可以最好地解释这一点:

SELECT *
FROM my_table
WHERE
    (string_col = :param1 OR :param1 IS NULL) AND
    (double_col = :param2 OR :param2 IS NULL);

这个 "trick" 背后的想法是,如果用户只指定一个参数,比如 :param1,我们可以只将 null 绑定到 :param2,并且 WHERE 子句的行为就好像只检查了第一个参数一样。理论上,这种方法让我们可以使用单个准备好的语句处理任意数量的输入参数,而不需要维护许多不同的语句。

我得到了一个简单的 POC,它使用纯 JDBC 准备好的语句在本地工作。但是,这样做需要在将参数与 NULL 进行比较之前转换参数,例如

WHERE (double_col = ? OR ?::numeric IS NULL)
                         ^^ does not work without cast

但是,我的实际应用程序使用的是 JPA,并且我不断收到以下持续性错误:

Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: double precision = bytea
  Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.

问题 不会 出现在 string/text 列上,但只出现在我的 Postgres table 中 double precision 的列上。我已经尝试了所有的铸造组合,但没有任何效果:

(double_col = :param2 OR CAST(:param2 AS double precision) IS NULL);
(CAST(double_col AS double precision) = :param2 OR :param2 IS NULL);
(CAST(double_col AS double precision) = :param2 OR CAST(:param2 AS double precision) IS NULL);

错误似乎是说 JDBC 正在向 Postgres 发送双列的 bytea 类型,然后 Postgres 翻滚了,因为它找不到转换 byte 到双精度。

Java 代码类似于:

Query query = entityManager.createNativeQuery(sqlString, MyEntity.class);
query.setParameter("param1", "some value");
// bind other parameters here
List<MyEntity> = query.getResultList();

作为参考,以下是我使用的所有版本:

Hibernate version         | 4.3.7.Final
Spring data JPA vesion    | 1.7.1.RELEASE
Postgres driver version   | 42.2.2
Postgres database version | 9.6.10
Java version              | 1.8.0_171

没有收到任何答案甚至评论形式的反馈,当我偶然发现这个优秀的博客时,我正准备放弃 post:

How to bind custom Hibernate parameter types to JPA queries

post 提供了两个选项来控制 JPA 通过驱动程序传递给 Postgres(或任何底层数据库实际是什么)的类型。我采用了使用 TypedParameterValue 的方法。这是我的代码继续上面给出的示例的样子:

Query query = entityManager.createNativeQuery(sqlString, MyEntity.class);
query.setParameter("param1", new TypedParameterValue(StringType.INSTANCE, null));
query.setParameter("param2", new TypedParameterValue(DoubleType.INSTANCE, null));
List<MyEntity> = query.getResultList();

当然,为查询中的 每个 参数传递 null 是微不足道的,但我这样做主要是为了显示文本的语法和双列。在实践中,我们希望至少有一些参数不是 null,但上面的语法处理所有值,null 或其他。

如果您想继续使用带有自动参数绑定的普通查询,您可以尝试以下方法。

WHERE (? IS NULL OR (CAST(CAST(? AS TEXT) AS DOUBLE PRECISION) = double_col

这似乎满足了 PostgreSQL 驱动程序的类型检查并产生了正确的结果。我没有做太多测试,但性能影响似乎很小,因为 CASTs 发生在常量值而不是数据库中的行上。