JPA/Hibernate 本机查询无法识别参数

Posted

技术标签:

【中文标题】JPA/Hibernate 本机查询无法识别参数【英文标题】:JPA/Hibernate Native Queries do not recognize Parameters 【发布时间】:2011-03-09 19:46:46 【问题描述】:

我正在使用 Hibernate/JPA 来执行本机 PostGIS 查询。这些查询的问题是它们需要的参数不是经典的 X = 'value' 形式。

例如,以下行崩溃

 String queryString = "select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(:lon :lat)'),4326), 0.1)";
  Query query = Cell.em().createNativeQuery(queryString, Cell.class);
  query.setParameter("lon", longitude);
  query.setParameter("lat", latitude);

play.exceptions.JavaExecutionException: org.hibernate.QueryParameterException: could not locate named parameter [lon]
 at play.mvc.ActionInvoker.invoke(ActionInvoker.java:259)
 at Invocation.HTTP Request(Play!)
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryParameterException: could not locate named parameter [lon]
 at org.hibernate.ejb.QueryImpl.setParameter(QueryImpl.java:358)

但是,以下查询有效:

String queryString = String.format("select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(%f %f)'),4326), 0.1)", longitude, latitude);
Query query = Cell.em().createNativeQuery(queryString, Cell.class);

(但它很容易被 SQL 注入...)

有谁知道在这种情况下如何使用setParameter()

【问题讨论】:

【参考方案1】:

也许你可以替换

'POINT(:lon :lat)'

'POINT(' || :lon || ' ' || :lat || ')'

这样参数在常量字符串之外,应该被查询解析​​器识别。

【讨论】:

+1 有趣的技巧实际上证实了问题来自引号,而不是来自命名参数(尽管 JPA 应该避免这种情况)。 在带有 NamedNativeQuery 注解的 Spring Data 中工作,因此不需要创建 Repository 实现。【参考方案2】:

没有为原生查询定义命名参数的使用。来自 JPA 规范(3.6.3 命名参数部分):

命名参数遵循以下规则 第 4.4.1 节中定义的标识符。 命名参数的使用适用于 Java 持久性查询语言, 并且没有为原生查询定义。 只有位置参数绑定可以 可移植地用于本机查询

因此请尝试以下方法:

String queryString = "select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(?1 ?2)'),4326), 0.1)";
Query query = Cell.em().createNativeQuery(queryString, Cell.class);
query.setParameter(1, longitude);
query.setParameter(2, latitude);

请注意,在 JPA >= 2.0 中,您可以在本机查询中使用命名参数。

【讨论】:

+1 引用了规范并提醒我这是不可移植的。我实际上在休眠项目的本机查询中使用了命名参数,它工作正常。在另一个较老的项目中,基于 toplink 要领,所有原生查询都使用位置参数,所以我之前一定知道并忘记了这一点:) @samokk:我想知道 POINT 周围的单引号是否不是这里的问题。 对于像 "?1" 这样的 JPA 位置参数,您必须调用 query.setParameter("1", longitude);等/ @Lord 位置参数应该使用Query.setParameter(int, Object)(而不是setParameter(String, Object) @Martin 理论上——是的,实际上——当时(2013 年)在 Hibernate 4.x 中,只有 setParameter(String, Object) 用于位置编号参数。对于非编号参数 ("?, ?, ?") setParameter(int, Object) 没问题。我不知道它是否仍然适用于 Hibernate 5。【参考方案3】:

因此,我们的想法是使用 Jörn Horstmann 建议的连接技巧来强制 postgres 识别参数。 以下代码有效:

String queryString = "select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(' || :lon || ' ' || :lat || ')'),4326), 0.2)";
Query query = Cell.em().createNativeQuery(queryString, Cell.class);
query.setParameter("lon", longitude);
query.setParameter("lat", latitude);

非常感谢您的回答!

【讨论】:

其实不是postgres没有识别参数,而是你的JPA提供者(因为单引号)。【参考方案4】:

你也可以摆脱整体

ST_GeomFromEWKT('POINT(' || :lon || ' ' || :lat || ')')

调用并替换为

ST_Point(:lon,:lat)

那么你就不用担心引号了。

【讨论】:

【参考方案5】:

Pascal 的回答是正确的,但是……您的解决方案如何容易发生 SQL 注入? 如果您在示例中使用String.format 和参数类型%f,那么除了数字之外的任何其他内容都会引发java.util.IllegalFormatConversionException。没有像 "xxx' OR 1=1 --" 这样的可能传递值。

小心,在String.format 中使用%s 已准备好进行SQL 注入。

【讨论】:

【参考方案6】:

我遇到了类似的问题,发现在原生查询中可以用问号设置参数。 试试这个:

String queryString = "select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(? ?)'),4326), 0.1)";

Query query = Cell.em().createNativeQuery(queryString, Cell.class);
query.setParameter(1, longitude);
query.setParameter(2, latitude);

【讨论】:

【参考方案7】:

我遇到了类似的问题。我在带有 ?1 的存储库中使用本机查询。它通过将参数括在括号中来解决它,如下所示。

SELECT * FROM XYZ WHERE ABC = (?1)

http://javageneralist.blogspot.com/2011/06/jpa-style-positional-param-was-not.html

【讨论】:

以上是关于JPA/Hibernate 本机查询无法识别参数的主要内容,如果未能解决你的问题,请参考以下文章

具有动态查询的 JPA 也是 Hibernate 中的动态(可选)参数

动态(复杂)查询 JPA/Hibernate 上的动态参数

Spring webflow + Jpa + Hibernate运行时无响应问题处理

如何在 JPA/Hibernate 中执行本机 SQL 脚本?

使用 JPA / Hibernate 在无状态应用程序中进行乐观锁定

无法在 FROM 子句中使用子查询编写 JPQL 查询 - Spring Data Jpa - Hibernate