【问题标题】:Simplifying Hibernate's Query简化 Hibernate 的查询
【发布时间】:2013-03-15 10:00:01
【问题描述】:

我有以下代码,它从 Oracle 数据库的单个表中执行一个简单的查询。

entityManager.createQuery(
        "SELECT a FROM " + Person.class.getSimpleName() 
        + " a WHERE lower(a.firstName) = '" + firstName + "'")
        .getSingleResult();

Hibernate 产生以下 sql:

select
        * 
    from
        ( select
            person0_.id as id75_,
            person0_.FIRSTNAME as FIRSTNAME75_,
            person0_.LASTNAME as LASTNAME75_
        from
            PERSONS person0_ 
        where
            lower(person0_.FIRSTNAME)='john' ) 
    where
        rownum <= ?

出于性能原因,我们的 DBA 建议此查询应该更简单。如何使休眠以简化查询 像这样:

select ID, FIRSTNAME, LASTNAME from PERSONS 
where lower(FIRSTNAEM) = 'john' and rownum <= 1

谢谢

【问题讨论】:

  • 首先我会在字段 FIRSTNAME 上创建一个索引。
  • @StefanBe: 并且该索引将被忽略,因为 where 子句正在使用函数。你需要一个功能索引...
  • 啊好的。不知道

标签: java oracle hibernate


【解决方案1】:

我刚刚查看了 explain plan 中与您类似的查询,并且两个查询的计划完全相同,所以我不确定您的 DBA 建议的性能原因是什么。

使用select * from ( ... ) where rownum = 1 包装查询会引入一个STOPKEY,它会在一行后停止内部查询。 Oracle 知道您实际上并不希望从子查询中获取所有结果,然后只获取第一行。

如果不修改 hibernate 源代码本身,就不可能更改 Hibernate 生成的查询。

注意,当您尝试引入ORDER BY 子句时,这种嵌套是必要的原因就很明显了:

select ID, FIRSTNAME, LASTNAME 
  from PERSONS 
 where lower(FIRSTNAME) = 'john' 
   and rownum <= 1
 order by LASTNAME

产生不同的结果

select * from (
    select ID, FIRSTNAME, LASTNAME 
      from PERSONS 
     where lower(FIRSTNAME) = 'john' 
     order by LASTNAME)
  where rownum <= 1

因为where rownumorder by clause 之前应用......

编辑:

这里是解释计划的输出作为参考,这两个查询完全相同:

---------------------------------------------------------------------------------
| Id  | Operation          | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |            |     1 |   112 |     2   (0)| 00:00:01 |
|*  1 |  COUNT STOPKEY     |            |       |       |            |          |
|*  2 |   TABLE ACCESS FULL| TABLE_NAME |     1 |   112 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------

可以通过在lower(FIRST_NAME) 上放置一个函数索引来提高性能,但这两个查询使用的索引完全相同。

【讨论】:

    【解决方案2】:

    我强烈建议您使用查询参数:

    Query query = entityManager.createQuery("SELECT a FROM "
        + Person.class.getSimpleName() 
        + " a WHERE lower(a.firstName) = :name");
    query.setParameter("name", firstName);
    return query.getSingleResult();
    

    这有两个重要原因:

    • 您可以防止 SQL 注入
    • 您允许 SQL 服务器缓存已解析的查询,从而提高后续执行性能

    考虑

    select * from (...) where rownum <= ?
    

    wrapper:这完全不消耗性能。你可以忽略它。

    【讨论】:

      【解决方案3】:

      我很惊讶,没有人建议重写为原生查询,而不是坚持使用 Hibernate 生成的内容。我有同样的问题,虽然我的与性能无关,但查询的结果从来都不一样,事实上查询很难理解。

      出于短期和长期原因的最佳方式是控制查询,随时随地使用本机查询。 只需按照 DBA 的建议编写 Query 并将其作为 JPQL NamedNativeQuery 放入,每个人都很高兴。享受吧!

      【讨论】:

        【解决方案4】:

        简而言之 - 你没有。几乎没有足够好的理由来调整 Hibernate 生成的查询,而且我非常怀疑在不修改源代码的情况下是否有可能。

        我能想到的唯一简化是调用getResultList().get(0) 而不是getSingleResult()。它会稍微简化生成的 SQL,但会导致性能下降,而不是改进,因为您将从 DB 中获取所有匹配的行。

        不过,您可以稍微改进一下查询。

        您知道要查询的类的简单名称。无需先从Entity中获取并连接,只需使用

        SELECT a FROM Person a...

        连接查询的参数不是一个好习惯。它使您的查询容易受到 SQL 注入攻击。最好写:

        ...WHERE lower(a.firstName) = :firstName");
        query.setParameter("firstName", firstName);
        

        【讨论】:

        • 我认为调用getResultList().get(0) 是一个坏主意,因为您将只返回数据库中的所有行,以便您的代码只使用第一行。让 Oracle 只返回一行会更好。
        • @beny23 是的,肯定会降低性能,但会稍微简化查询。我现在已经在答案中说得更清楚了,还不是很清楚。
        • 如果 getResultList() 期望通过在 where 子句中使用唯一字段返回 1 行怎么办?
        • @RueLeonheart - 我不确定数据库是否会足够聪明,可以在第一场比赛后停止,就像在getSingleResult 上一样。由于还有更多工作要做 - 创建列表等,因此它的效率可能仍然会降低一些。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-09
        • 2018-02-16
        • 2022-01-08
        • 2012-09-20
        • 2014-03-19
        • 1970-01-01
        相关资源
        最近更新 更多