Hibernate:如何使用HQL设置空查询参数值?

62

我该如何将 Hibernate 参数设置为“null”?例如:

Query query = getSession().createQuery("from CountryDTO c where c.status = :status  and c.type =:type")
.setParameter("status", status, Hibernate.STRING)
.setParameter("type", type, Hibernate.STRING);

在我的情况下,状态字符串可能为null。我已经调试了它,然后Hibernate生成一个这样的SQL字符串/查询...status = null... 但是,在MYSQL中不起作用,因为正确的SQL语句必须是"status is null"(根据我阅读的mysql文档,Mysql不理解status=null,并将其计算为false,因此查询将永远不会返回任何记录...)

我的问题:

  1. 为什么Hibernate无法将空字符串正确转换为"is null"(而是错误地创建"= null")?

  2. 重写此查询的最佳方法是什么,使其具有空值安全性?具有空值安全性意味着在"status"字符串为空的情况下,它应该创建一个"is null"?


1
对于所有对解决方案感兴趣的人,我认为Criteria API是一种方式。但我仍然不太确定,因为它会使代码变得非常臃肿,而使用HQL将更加清晰。也许真正的解决方案在于实现自己的Hibernate类型(我已经为ENUM实现了一个,但这些自实现类型,至少基本类型,在使用HQL查询时具有与内建Hibernate类型相比的巨大劣势(除非您还扩展了HQL解析器?)这使得这成为一个巨大的项目,并需要大量的Hibernate知识...(续第二部分) - tim
也许最简单的方法(然而绝对不是最佳实践)是直接编辑Hibernate String类型类本身并添加缺失的逻辑。这只需要几行代码来检查空字符串值并相应地采取行动... - tim
5
我也遇到了这个问题......天啊,这太蠢了。 - George Mauer
1
我真的很讨厌Hibernate,这太愚蠢了。 - B T
1
这么多年过去了,有没有不使用 Criteria API 的解决方案呢? - phpslightly
1
@phpslightly 我简直不敢相信这还没有呢! - Muhammad Bekette
10个回答

40
  1. 我认为Hibernate会首先将你的HQL查询翻译成SQL,然后才尝试绑定你的参数。这意味着它无法将查询从param = ?重写为param is null

  2. 尝试使用Criteria API:

Criteria c = session.createCriteria(CountryDTO.class);
c.add(Restrictions.eq("type", type));
c.add(status == null ? Restrictions.isNull("status") : Restrictions.eq("status", status));
List result = c.list();

1
你好Sergey,感谢你的回答。实际上,在你发表回答之前,我已经使用了Criteria API来实现它。但是,我仍然认为这不是解决问题的“理想方案”。说实话,有时候“怪异/错误”没有真正好的解决方案...我已将您的答案标记为问题的“解决方案”。 - tim
但是您对理想解决方案有什么期望呢?如果您可以接受使用 Criteria API 而不是 HQL,那么您可以随时为“null-safe”Criterion 创建自己的工厂方法。 - Sergey Demin
非常棒的回答!这个简单的修复帮助我针对可空字段进行查询,而不需要将查询字符串构建起来! - cdeszaq

35

这不是一个特定于Hibernate的问题(这只是SQL的性质),对于SQL和HQL都有解决方案:

@Peter Lang的想法是正确的,你也有正确的HQL查询。我猜你只需要重新运行一遍以获取查询更改结果;-)

下面的代码绝对有效,如果您将所有查询保留在orm.xml中,则非常好

from CountryDTO c where ((:status is null and c.status is null) or c.status = :status) and c.type =:type

如果您的参数字符串为null,则查询将检查该行的状态是否为null。否则,它将回归等号比较。

注:

问题可能是特定于MySql的怪癖。我只使用Oracle进行了测试。

上述查询假定存在c.status为null的表行

where子句优先考虑参数。

参数名称"type"可能是SQL中的保留字,但这并不重要,因为它在查询运行之前被替换。

如果您需要跳过:status where_clause,可以编写以下代码:

from CountryDTO c where (:status is null or c.status = :status) and c.type =:type

它相当于:

sql.append(" where ");
if(status != null){
  sql.append(" c.status = :status and ");
}
sql.append(" c.type =:type ");

为什么不使用 from CountryDTO c where (:status is not null and c.status = :status) and c.type =:type - Alex78191
@Alex78191 使用or关键字,查询语句将返回:from CountryDTO c where (true) and c.type =:type 而你的查询将返回:from CountryDTO c where (false) and c.type =:type,这会导致查询结果为空。 - egallardo
@Alex78191 你也可以尝试这个:select 'data' from dual where (1=2) 和这个:select 'data' from dual where (1=1) 来看看它们之间的区别。 - egallardo

15

你好,skaffman。非常感谢你的帮助!当我提出问题时,实际上我认为自己太傻了,找不到解决方案。但似乎这是Hibernate的一个真正的“怪异之处”。最有可能的是我将不得不编写自己的Nullable-String-Type,或者我将不得不切换到Criteria Queries...深入挖掘后,似乎Hibernat.STRING类型的当前实现是不可空的: http://www.docjar.com/html/api/org/hibernate/type/StringType.java.html。(但是MySQL JConnector应该正确实现PreparedStatment.setString吧?)谢谢Tim。 - tim
2
仅供参考,这实际上是JDBC的“特性”:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4312435 Hibernate使用JDBC的'PreparedStatement.setNull()'来绑定'null'参数。 - axtavt

5

看起来你需要在HQL中使用is null(如果有多个可能为空的参数,这可能会导致复杂的排列组合),但是这里有一个可能的解决方案:

String statusTerm = status==null ? "is null" : "= :status";
String typeTerm = type==null ? "is null" : "= :type";

Query query = getSession().createQuery("from CountryDTO c where c.status " + statusTerm + "  and c.type " + typeTerm);

if(status!=null){
    query.setParameter("status", status, Hibernate.STRING)
}


if(type!=null){
    query.setParameter("type", type, Hibernate.STRING)
}

3
看起来像是旧的 SQL 查询构建方式。对于这种情况,请使用 Criteria API。手写 HQL 容易出现极端错误。 - whiskeysierra

5

HQL支持coalesce,允许使用丑陋的解决方法,例如:

where coalesce(c.status, 'no-status') = coalesce(:status, 'no-status')

4

对于一个实际的HQL查询:

FROM Users WHERE Name IS NULL

4
我没有尝试过这个,但是当你使用:status两次来检查NULL时会发生什么?
Query query = getSession().createQuery(
     "from CountryDTO c where ( c.status = :status OR ( c.status IS NULL AND :status IS NULL ) ) and c.type =:type"
)
.setParameter("status", status, Hibernate.STRING)
.setParameter("type", type, Hibernate.STRING);

你好 Peter,非常感谢你的帮助! 我不知道为什么,但似乎 hibernate 总是将 ( c.status = :status OR ( c.status IS NULL AND :status IS NULL ) 评估为 c.status = null(根据我的 mysql 查询日志)。 我还改变了顺序为 (( c.status IS NULL AND :status IS NULL ) OR c.status = :status),结果相同。谢谢 Tim - tim

2
您可以使用

标签


Restrictions.eqOrIsNull("status", status)

代替
status == null ? Restrictions.isNull("status") : Restrictions.eq("status", status)

1

这是我在Hibernate 4.1.9上找到的解决方案。我必须向我的查询传递一个有时可能为NULL的参数。所以我使用了以下方法:

setParameter("orderItemId", orderItemId, new LongType())

之后,我在我的查询中使用以下where子句:

where ((:orderItemId is null) OR (orderItem.id != :orderItemId))

正如您所看到的,我正在使用Query.setParameter(String, Object, Type)方法,其中我无法使用文档中找到的Hibernate.LONG(可能是旧版本)。有关类型参数的完整选项集,请检查org.hibernate.type.Type接口的实现类列表。
希望这能帮助您!

-1

这似乎也可以工作 ->

@Override
public List<SomeObject> findAllForThisSpecificThing(String thing) {
    final Query query = entityManager.createQuery(
            "from " + getDomain().getSimpleName() + " t  where t.thing = " + ((thing == null) ? " null" : " :thing"));
    if (thing != null) {
        query.setParameter("thing", thing);
    }
    return query.getResultList();
}

顺便说一下,我在这方面还很新,如果有任何不好的想法,请告诉我。谢谢。


你应该使用 is 运算符:即 ... + " t where t.thing " + ((thing == null) ? "is null" : "= :thing") - bvdb
你能详细解释一下这两者之间的区别吗? - Wim Berchmans
2
使用= null会导致异常。虽然这可能取决于您运行的数据库管理系统。在SQL中,您也有相同的问题,那里您必须使用is null而不是= null。关于null的问题是,一些数据库认为它并不真正作为空值,而是作为未确定的值,这意味着null不等于null。因此,null=null永远不会成立。请参见:https://dev59.com/mG445IYBdhLWcg3wDV8P - bvdb

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