使用Spring Data JpaRepository按计数排序

2022-09-04 01:08:12

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

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

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

我必须按以下条件排序bes.size()

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


答案 1

我使用来自以下方面的提示和灵感解决了这个难题:

  1. 使用 Koitoer 的@Query注释限制结果集
  2. 如何在 JPA 中按 count() 排序 MicSim
  3. 我自己做详尽的实验

关于 ,我一直没有意识到的第一件也是最重要的事情是,即使使用自定义方法,人们仍然可以通过简单地将对象作为参数传递来创建分页查询。这是弹簧数据文档可以明确说明的,因为它绝对不明显,尽管功能非常强大。@QueryPageable

太好了,现在是第二个问题 - 我如何在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);
}

...以及服务级别的一些附加条件逻辑(可能用抽象存储库实现封装)。因此,尽管不是非常优雅,但这使技巧 - 这种方式(具有更复杂的实体)我可以按其他属性进行排序,进行过滤和分页。


答案 2

一个选项(比原始解决方案简单得多,并且还具有其他好处)是创建聚合数据的数据库视图,并通过 或 将实体链接到此视图。@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 进行排序、归档、查询等。

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


推荐