【问题标题】:Dynamic HQL query using hql expressions rather than Criteria?使用 hql 表达式而不是 Criteria 的动态 HQL 查询?
【发布时间】:2012-05-17 17:43:04
【问题描述】:

出于各种原因,我正在尝试编写部分动态的 HQL 查询而不使用 Criteria API。我想知道是否有一种简单的方法可以使用 HQLs 表达式来短路 where 限制。例如,这是可以正常工作的原始查询:

SELECT customer 
FROM Customer as customer 
INNER JOIN customer.profile as profile 
WHERE profile.status IN :statusCodes
AND   profile.orgId IN :orgIds

StatusCodes 是字符串列表,orgIds 是整数列表。但是,任何一个都是可选的,如果传递 null 而不是集合,则不应限制。我试图这样做:

SELECT customer 
FROM Customer as customer 
INNER JOIN customer.profile as profile 
WHERE (:statusCodes IS NULL OR profile.status IN :statusCodes)
AND   (:orgIds IS NULL OR profile.orgId IN :orgIds)

不幸的是,这不起作用,但是否有任何其他方法可能有效,无论是使用不同的表达式还是传入默认值?

编辑:为了清楚起见,我正在寻找一种使用 NamedQuery 的方法,而不是以任何方式动态构建查询。

解决方案:我使用额外的查询参数来完成它。我创建了两个辅助方法:

private void setRequiredParameter(TypedQuery<?> query, String name, Object value) {
    query.setParameter(name, value);
}

private void setOptionalParameter(TypedQuery<?> query, String name, Object value) {
    query.setParameter(name, value);
    query.setParameter(name + "Optional", value == null ? 1 : 0);
}

这样的查询:

SELECT customer 
        FROM Customer as customer 
        INNER JOIN  customer.profile as profile 
        WHERE (:statusCodesOptional = 1 OR profile.status IN :statusCodes)
        AND (:orgIdsOptional = 1 OR profile.orgId  IN :orgIds)

【问题讨论】:

    标签: java hibernate hql


    【解决方案1】:

    我的建议是将所有参数放在地图中并动态构建查询,在构建之后执行之前设置查询所需的所有参数,从地图中获取值:

    Map<String, Object> pars = new HashMap<String,Object>();
    pars.put("statusCodes", statusCodes);
    pars.put("orgIds", orgIds);
    
    StringBuilder b = "SELECT customer FROM Customer as customer INNER JOIN customer.profile as profile where 1 = 1";
    if (statusCodes != null) {
      b.append(" and profile.status in :statusCodes");
    }
    if (orgIds != null) {
      b.append(" and profile.orgId in :statusCodes");
    }
    
    ...
    
    Query q = session.createQuery(b.toString());
    
    ...
    
    for (String p : q.getNamedParameters()) {
      q.setParameter(p, pars.get(p));
    }
    

    当然需要一些改进,例如在未设置参数时抛出异常,如果复杂度大于几个简单参数则使用类型化参数等等。

    【讨论】:

    • SQL注入概率如何?
    • 在这种情况下没有机会进行sql注入,它被作为参数处理,因此它的内容被转义了。
    【解决方案2】:

    如果您绝对必须避免动态查询,您可以这样做,但要牺牲两个额外的参数:

    SELECT customer 
      FROM Customer AS customer 
      JOIN customer.profile AS profile 
     WHERE (profile.status IN :statusCodes OR :statusCodeCount = 0)
       AND (profile.orgId IN :orgIds OR :orgIdCount = 0)
    

    在您的 Java 代码中,您将执行以下操作:

    session.getNamedQuery("your.query.name")
           .setParameterList("statusCodes", statusCodes)
           .setParameter("statusCodeCount", statusCodes.length)
           .setParameterList("orgIds", orgIds)
           .setParameter("orgIdCount", orgIds.length);
    

    您需要确保数组长度为零而不是 null,或者提供额外的 if 检查以处理 null 场景。

    话虽如此,HQL 确实更适合定义明确的(例如静态)查询。您可以解决动态参数,但无法解决动态排序。

    【讨论】:

    • 谢谢,这是我的怀疑,虽然我仍然希望有一种方法可以使用表达式来避免额外的参数。
    • @user842800 没有 - 不适用于列表。 Hibernate 的查询处理器必须在幕后做一些魔术来支持列表(具体来说,计算元素的数量并在结果 SQL 中生成适当数量的位置参数),因此传入 null 不是一种选择。
    • SQL 注入的可能性如何?
    【解决方案3】:

    您必须动态生成查询:

    StringBuilder hql = 
        new StringBuilder("SELECT customer FROM Customer as customer INNER JOIN customer.profile as profile where 1 = 1")
    if (statusCodes != null) {
        hql.append(" and profile.status IN :statusCodes");
    }
    if (orgIds != null) {
        hql.append(" and profile.orgId IN :orgIds");
    }
    

    当然,您还必须为查询设置参数,前提是它们不为空。

    【讨论】:

    • 谢谢,但我尽量避免编写任何动态查询。我希望查询存在于 .hbm 文件中,因为有些不可避免地需要调整或转换为本机查询。所以必须有某种方法将它们作为命名查询来维护,而不是动态构造它们。
    • 然后,AFAIK,您将需要 4 个不同的查询:每个可能的组合(null-null、notnull-null、null-notnull、notnull-notnull)一个。你只需要希望你不需要第三个参数。请注意,我真的不明白在 xml 文件中将静态 HQL 更改为 SQL 比在 Java 文件中将动态 HQL 更改为动态 SQL 容易得多。
    • 好处来自应用程序不需要知道命名查询是 hql 还是 sql。它们甚至可以由 dba 简单地在 xml 文件中换出。我希望使用 OR 条件来避免多个查询。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-18
    • 2021-04-05
    • 1970-01-01
    • 2018-10-18
    • 1970-01-01
    • 2012-08-19
    相关资源
    最近更新 更多