【问题标题】:Order by count using Spring Data JpaRepository使用 Spring Data JpaRepository 按计数排序
【发布时间】:2014-06-01 22:06:40
【问题描述】:

我正在使用 Spring Data JpaRepository,我发现它非常易于使用。我实际上需要所有这些功能——分页、排序、过滤。不幸的是,有一件令人讨厌的小事似乎迫使我转而使用普通的 JPA。

我需要按相关集合的大小进行排序。例如我有:

@Entity
public class A{
    @Id
    private long id;
    @OneToMany
    private List<B> bes;
//boilerplate
}

我必须按bes.size()排序

有没有办法以某种方式自定义排序,仍然利用分页、过滤和其他 Spring Data 的强大功能?

【问题讨论】:

    标签: java jpa spring-data


    【解决方案1】:

    我使用以下提示和灵感解决了这个难题:

    1. Limiting resultset using @Query anotations 来自 Koitoer
    2. How to order by count() in JPA 来自 MicSim
    3. 我自己的详尽实验

    关于,我不知道的第一件也是最重要的事情是,即使使用@Query 自定义方法,仍然可以通过简单地将Pageable 对象作为参数传递来创建分页查询。这是 spring-data 文档可以明确说明的内容,因为它虽然非常强大,但绝对不是显而易见的。

    太好了,现在是第二个问题 - 我如何在 JPA 中按关联集合的大小对结果进行实际排序?我设法找到了以下 JPQL:

    select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a
    

    其中AwithBCount是查询结果实际映射到的类:

    public class AwithBCount{
        private Long bCount;
        private A a;
    
        public AwithBCount(Long bCount, A a){
            this.bCount = bCount;
            this.a = a;
        }
        //getters
    }
    

    很高兴我现在可以像下面这样简单地定义我的存储库

    public interface ARepository extends JpaRepository<A, Long> {
        @Query(
            value = "select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a",
            countQuery = "select count(a) from A a"
        )
        Page<AwithBCount> findAllWithBCount(Pageable pageable);
    }
    

    我赶紧尝试了我的解决方案。完美 - 页面已返回,但当我尝试按 bCount 排序时,我感到很失望。事实证明,由于这是一个 ARepository(不是 AwithBCount 存储库),spring-data 将尝试在 A 中查找 bCount 属性而不是 AwithBCount。所以最后我得到了三个自定义方法:

    public interface ARepository extends JpaRepository<A, Long> {
        @Query(
            value = "select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a",
            countQuery = "select count(a) from A a"
        )
        Page<AwithBCount> findAllWithBCount(Pageable pageable);
    
        @Query(
            value = "select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a order by bCount asc",
            countQuery = "select count(a) from A a"
        )
        Page<AwithBCount> findAllWithBCountOrderByCountAsc(Pageable pageable);
    
        @Query(
            value = "select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a order by bCount desc",
            countQuery = "select count(a) from A a"
        )
        Page<AwithBCount> findAllWithBCountOrderByCountDesc(Pageable pageable);
    }
    

    ...以及服务级别的一些附加条件逻辑(可能用抽象存储库实现封装)。所以,虽然不是非常优雅,但这就是诀窍 - 这样(拥有更复杂的实体)我可以按其他属性排序,进行过滤和分页。

    【讨论】:

    • 很好用 JPQL 很有趣你解决的方法,谢谢分享太详细了
    • 我看不到c 设置在哪里AwithBCount(count(b.id) as bCount,c),你不需要写count(b.id) as bCount 只需count(b.id) 就足够了
    • 此解决方案对我不起作用 - 是否可以返回与 A 不同类型的 Page?!在尝试此解决方案时,我收到一条错误消息,指出 AwithBCount 是未映射的实体。
    • 这个解决方案对我不起作用,提到 AwithBCount 不是托管类型。
    • 我认为这个解决方案从 Spring boot 2.x 开始就不能使用了。实现似乎已经改变:存储库只能有返回链接到它的实体的方法,或者在计数的情况下返回一个数字(long/int)。由于您的 AwithBCount 没有存储库,因此您会收到此错误。所有与存储库相关的实体都存储在某种映射中:实体 -> 该实体的存储库;并且 AwithBCount 不是一个实体。一种解决方案是在 Controller 或不是存储库的 Service 中编写一个专用方法,该方法执行您编写的 JPQL。
    【解决方案2】:

    我对 Spring Data 了解不多,但对于 JPQL,要按关联集合的大小对对象进行排序,您可以使用查询

    Select a from A a order by a.bes.size desc
    

    【讨论】:

      【解决方案3】:

      一个比原始解决方案简单得多并且还有其他好处的选项是创建聚合数据的数据库视图并通过@SecondaryTable@OneToOne 将您的实体链接到此。

      例如:

      create view a_summary_view as
      select
         a_id as id, 
         count(*) as b_count, 
         sum(value) as b_total, 
         max(some_date) as last_b_date 
      from b 
      

      使用@SecondaryTable

      @Entity
      @Table
      @SecondaryTable(name = "a_summary_view", 
             pkJoinColumns = {@PrimaryKeyJoinColumn(name = "id", referencedColumnName= "id")})
      public class A{
      
         @Column(table = "a_summary_view")
         private Integer bCount;
      
         @Column(table = "a_summary_view")
         private BigDecimal bTotal;
      
         @Column(table = "a_summary_view")
         private Date lastBDate;
      }
      

      您现在可以纯粹参考实体 A 进行排序、归档、查询等。

      作为您在域模型数据中的一个额外优势,在内存中计算可能会很昂贵,例如客户所有订单的总价值,无需加载所有订单或恢复到单独的查询。

      【讨论】:

        【解决方案4】:

        谢谢@Alan Hay,这个解决方案对我来说很好用。我只需要设置@SecondaryTable 注释的foreignKey 属性,一切正常(否则Spring Boot 尝试向id 添加外键约束,这会引发sql View 错误)。

        结果:

        @SecondaryTable(name = "product_view",
                    pkJoinColumns = {@PrimaryKeyJoinColumn(name = "id", referencedColumnName = "id")},
                    foreignKey = @javax.persistence.ForeignKey(ConstraintMode.NO_CONSTRAINT))
        

        【讨论】:

          【解决方案5】:

          您可以将 select 子句中的属性名称用作排序属性:

          @Query(value = "select a, count(b) as besCount from A a join a.bes b group by a", countQuery = "select count(a) from A a")
              Page<Tuple> findAllWithBesCount(Pageable pageable);
          

          您现在可以对属性 besCount 进行排序:

          findAllWithBesCount(PageRequest.of(1, 10, Sort.Direction.ASC, "besCount"));
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-01-23
            • 2013-11-13
            • 1970-01-01
            • 1970-01-01
            • 2020-06-24
            • 1970-01-01
            • 1970-01-01
            • 2014-05-20
            相关资源
            最近更新 更多