Spring Data JPA:使用连接表进行排序和分页

9

我有一个场景需要对3个表中的结果进行过滤、排序和分页。

目前,我使用Spring Data JPA的Specification功能来处理单个实体:repository.findAll(specification, pageRequest)

这很好用,但现在我有另一个场景,排序/筛选属性分布在由一对多关系连接的3个表中。

以下是我的具体情况:

@Entity
public class CustomerEntity ... {
  ...

  @Column(nullable = false)
  public String                                 customerNumber;

  @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
  public List<CustomerItemEntity> items;
}


@Entity
public class CustomerItemEntity ... {
  ...

  @Column(nullable = false)
  public String                                 itemNumber;

  @ManyToOne(optional = false)
  @JoinColumn(name = "customerId")
  public CustomerEntity customer;

  @OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval = true)
  public List<DocumentEntity> documents;
}


@Entity
public class DocumentEntity ... {
  ...

  @Column(nullable = false)
  public LocalDate                                 validDate;

  @ManyToOne(optional = false)
  @JoinColumn(name = "itemId")
  public CustomerItemEntity item;
}

有没有一种方法可以同时使用PageRequestSpecification来过滤、排序和分页customerNumberitemNumbervalidDate?


不要忘记接受/点赞那些帮助过你的答案。 - Cepr0
2个回答

19

尝试像这样做:

Specification<CustomerEntity> joins = (customer, query, cb) ->  {
    // from CustomerEntity c
    // join c.items i
    Join<CustomerEntity, CustomerItemEntity> items = customer.join("items");

    // join i.documents d
    Join<CustomerItemEntity, DocumentEntity> documents = items.join("documents");

    // // where c.customerNumber = ?1 and i.itemNumber = ?2 and d.validDate = ?3 
    return cb.and( 
            customer.equal(customer.get("customerNumber", customerNumber)),
            items.equal(items.get("itemNumber", itemNumber)), 
            documents.equal(documents.get("validDate", validDate))
    );
};

// sort by c.customerNumber asc
PageRequest pageRequest = new PageRequest(0, 2, new Sort(Sort.Direction.ASC, "customerNumber"));

Page<CustomerEntity> customerPage = CustomerRepo.findAll(joins, pageRequest);

但我不知道为什么你需要在这里使用规格说明

你可以让它更简单:

@Query("select c from CustomerEntity c join c.items i join i.documents d where c.customerNumber = ?1 and i.itemNumber = ?2 and d.validDate = ?3")
Page<CustomerEntity> getCustomers(String customerNumber, String itemNumber, LocaleDate validDate, Pageable pageable);  

但是,由于你的三个实体具有顺序一对多的关系,所有这些都没有意义。在这种情况下,您只需使用最后一个条件:where d.validDate = ?1,而不是三个条件。然后查询方法会更加简单:

@Query("select c from CustomerEntity c join c.items i join i.documents d where d.validDate = ?1")
Page<CustomerEntity> getCustomers(LocaleDate validDate, Pageable pageable);

更新

若要按照加入实体的字段进行排序,可以使用queryorderBy方法:

Specification<CustomerEntity> joins = (customer, query, cb) ->  {

    Join<CustomerEntity, CustomerItemEntity> items = customer.join("items");
    Join<CustomerItemEntity, DocumentEntity> documents = items.join("documents");

    // Ascending order by 'Document.itemNumber'
    query.orderBy(cb.asc(documents.get("itemNumber")));

    return cb.and( 
            customer.equal(customer.get("customerNumber", customerNumber)),
            items.equal(items.get("itemNumber", itemNumber)), 
            documents.equal(documents.get("validDate", validDate))
    );
};

Page<CustomerEntity> customerPage = CustomerRepo.findAll(joins, new PageRequest(0, 2));

要按多个参数排序,可以将它们以逗号分隔的方式传递给该方法,或使用List

query.orderBy(cb.asc(items.get("customerNumber")), cb.desc(documents.get("itemNumber")));

1
谢谢,这对我有用。顺便说一下,我使用规范构建它,因为过滤器是可选的。但我还有一个问题:如何按其中一个连接列排序?我不能仅在PageRequest中定义它们,因为JPA会告诉我CustomerEntity中没有属性。您有想法如何按例如itemNumber排序吗? - Tobi
谢谢,但我在提取正确的结果方面遇到了问题。当我按照特定客户、特定项目和具有特定有效日期的文档进行筛选时,页面对象中的元素数量是正确的。但是,当我访问页面内容时,即客户实体时,我得到了这个客户实体的所有项目和文档,尽管我只应该得到一个项目和一个文档。你有什么办法可以提取正确的结果吗? - Tobi
1
@Tobi,我认为你应该发布一个新问题,在其中展示你当前的查询/结果并展示你期望的结果。没有看到它们很难理解。 - Cepr0
1
最终我使用JPA的Criteria API创建了自己的Spring数据仓库以返回元组作为结果。无论如何还是谢谢。 - Tobi
1
我确实理解这个例子,它很好用,但是...如果定义排序字段的参数来自Swagger,我该如何让它工作呢?基本上,它只适用于不属于联接的字段,或者如果我也指定表名(例如jointable.field),但是,显然,我的用户不知道这些字段来自哪个表... - Pitto
显示剩余4条评论

2

我发现你可以使用自定义查询创建的连接别名来按OneToMany列的内容进行排序。在我的代码库中,它是这样的:

@Query("select p from Person p join p.roles r")
Page<Person> search(Pageable pageable);

所以我可以使用以下方法进行排序:

r.role

使用控制器传递:

@PageableDefault(size = 15, sort = "r.role") Pageable pageable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接