【问题标题】:Hibernate Select Top and Bottom n Rows with Criteria休眠选择带有条件的顶部和底部n行
【发布时间】:2011-07-31 05:08:20
【问题描述】:

假设我有两张表,书籍和评论。评论有一个星号列,其值可以在 1 到 5 之间。一本书可以有很多评论。

如何选择所有图书,以便使用 Criteria API 仅返回每本书的顶部和底部 3 条评论(而不是所有评论)?

如果 Criteria API 不支持,我愿意接受 HQL、SQL 等其他建议。

【问题讨论】:

    标签: java hibernate


    【解决方案1】:

    你总是可以设置顺序 asc/desc 然后限制结果。

    例如:

    criteria.addOrder(Order.desc("id"));
    criteria.setMaxResults(1);
    

    不确定它在效率方面的位置。我假设它是在 Select * 被触发之后进行过滤的?

    【讨论】:

      【解决方案2】:

      试试这个,让我知道它是否适合你:

        // you should have the DAO class containing this queries 
        // extend HibernateDaoSupport
      
        List<Tag> topList = getSession().createQuery("FROM Reviews r WHERE r.book = " 
                + book + " ORDER BY r.stars asc").setMaxResults(3).list();
      
        List<Tag> bottomList = getSession().createQuery("FROM Reviews r WHERE r.book = " 
                + book + " ORDER BY r.stars desc").setMaxResults(3).list();
      

      【讨论】:

      • 谢谢。是的,我最终在我的 Review DAO 中添加了类似的内容,但我更喜欢 Criteria API,所以我使用了它。问题是,我正在查询符合特定条件的所有书籍,并且每本书都需要有其顶部和底部评论。运行该查询然后手动检查每个评论,运行这两个查询并调整响应,这将是非常低效和尴尬的。我已经确定了一个不同的解决方案。
      • 不要将 sql 写成字符串连接。这非常容易受到注入攻击。考虑有人将其作为变量 book 输入:“any; drop table Reviews”
      【解决方案3】:

      回答我自己的问题...

      我真的很惊讶这是多么困难。所有涉及查询书籍的解决方案以及每本书都有其顶部和底部评论列表会导致在某些数据库上每本书 N 次查询,特别是我的(MySql)需要 3N。当然,有人可能会想出一个聪明的方法来解决这个问题,但是我玩代码、原始 SQL、研究其他人所做的事情、数据库限制等似乎总是会回到那个问题上。

      所以...我最终把问题反过来了。每当我查询书籍时(这是一个非常频繁的查询 - 仅供参考的书籍/评论是出于示例目的 - 实际域不同),我不会在每次提交新评论时计算顶部/底部评论,而不是计算顶部和底部评论。这会将提交评论从一个“查询”更改为三个查询,但与查询图书相比,提交评论的频率相当低。

      为此,我向 Book 添加了两个新属性 topReviews 和 bottomReviews 并将它们映射为 Review 上的一对多列表。然后,我更新了保存新评论的代码,以查询相关图书的顶部和底部评论(2 个查询),设置图书的顶部/底部评论属性(覆盖前一个),并保存图书。现在,任何图书查询都会返回这些“缓存”的顶部和底部评论。如果需要,我仍然有一个惰性“评论”属性,它将包含所有评论(不仅仅是顶部/底部) - 另一个一对多映射。

      我愿意接受其他建议。这里的大多数答案都在球场上,但由于数据库限制或需要太多查询而错过了问题或无法工作。

      【讨论】:

        【解决方案4】:

        我认为你不能使用单个查询来做你想做的事。但是,您当然可以分两步完成(使用带有命名参数的 EJBQL):

        SELECT r FROM Reviews r WHERE r.book = :book ORDER BY r.stars ASC LIMIT 3;
        SELECT r FROM Reviews r WHERE r.book = :book ORDER BY r.stars DESC LIMIT 3;
        

        您当然可以编写一个简单的辅助方法来进行这两个调用并将结果整理到您喜欢的任何数据结构中。

        【讨论】:

        • LIMIT 是 RDMS 特定的。不要使用它。使用setMaxResults
        • @Alex - LIMIT 也是EJBQL spec 的一部分。
        • 你是对的。我从来没有用过它。不过,他想要条件查询。
        • 问题似乎也略有不同。他想要书而不是评论。我现在撤回了我的答案,因为我不相信我回答的问题是正确的。
        • @Alex - 好点子,他想“选择所有书籍,只返回每本书的顶部和底部 3 条评论”,我认为这不是直接可能的。通常,当我希望一个实体为某个其他实体的集合提供“自动”自定义过滤时,我会向第一个实体添加一个 @Transient 方法,该方法对支持集合执行所需的过滤,然后返回结果(在新集合中实例)。
        【解决方案5】:

        这通常使用联合之类的方法来完成,而在 HQL 中是无法做到的。

        不过,您可以在 HQL 中执行此操作

        WHERE id in ([SELECT top 3]) or id in ([SELECT bottom 3])
        

        我没有办法对此进行测试,但这也可能有效。

        DetachedCriteria topN = [ your criteria ] 
        DetachedCriteria bottomN = [ your criteria ] 
        
        session.createCriteria(..._
            .add( Property.or(Property.forName("id").in(topN),Property.forName("id").in(bottomN) )
            .list();
        

        【讨论】:

        • 谢谢。我很确定我不能使用您的 Criteria 建议,因为 DetachedCriteria 没有 setMaxResults() 方法,我需要它来将结果限制为顶部/底部 3(实际上在 Hibernate 的 JIRA 站点中有一个请求添加此功能)。不过,我会试试你的 HQL。
        • 看来你的建议,一般来说,我想要的,对于 Criteria、HQL 是不可能的,据我所知,甚至 SQL(至少在 MySQL 上)。我可能会翻转这个问题并在保存评论时进行一些预处理,以便将每本书的顶部/底部评论保存在一个特殊的表格中。
        • 我很确定这在 MySQL 中是可能的,请发布您的查询,我会考虑一下
        • MySQL 不支持subselects with limits :-(
        • 呸,好收获。大多数其他方言都支持这一点,我在 MSSQL 中尝试过。一个讨厌的解决方案是临时表。如果您发现更好的东西,请发布
        【解决方案6】:

        如何将 Review 和 Book 映射为多对多,然后在 Book 的评论集合中指定:order-by="rating desc" 和 lazy="true"

        假设使用延迟获取方法,仅当要从集合中获取对象时才会获取数据,但我不确定这一点。要确认,您可以打开 Hibernate SQL 日志记录并监控在运行时进行了哪些查询。

        【讨论】:

        • 我需要顶部和底部评论,所以这不太行。看我的回答。谢谢!
        【解决方案7】:

        使用 org.springframework.data.domain.Pageable 的两个 HQL 查询,例如:

        在中间层服务中创建可分页并调用 repo 之前:

        Pageable pageableTop = new PageRequest(0, 3, Direction.ASC, "yourFieldToSortBy");
        yourRepository.findTopOrderByYourEntity(pageableTop);
        
        Pageable pageableBottom = new PageRequest(0, 3, Direction.DESC, "yourFieldToSortBy");
        yourRepository.findTopOrderByYourEntity(pageableBottom);
        

        回购:

        public interface YourRepository extends CrudRepository<YourEntity, String> {
        
            @Query("FROM YourEntity")
            List<YourEntity> findTopOrderByYourEntity(Pageable pageable);
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-05-04
          • 1970-01-01
          • 2015-11-25
          • 1970-01-01
          • 1970-01-01
          • 2010-10-18
          • 1970-01-01
          相关资源
          最近更新 更多