ImplementSearchspecificationforpagination

I implemented this Page request:

@GetMapping
public PageImpl<ProductFullDTO> list(@RequestParam(name = "page", defaultValue = "0") int page,
                                     @RequestParam(name = "size", defaultValue = "10") int size) {
    PageRequest pageRequest = PageRequest.of(page, size);
    PageImpl<ProductFullDTO> result = productRestService.page(pageRequest);
    return result;
}

public PageImpl<ProductFullDTO> page(PageRequest pageRequest){

        Page<Product> pageResult = productService.findAll(pageRequest);
        List<ProductFullDTO> result = pageResult
                .stream()
                .map(productMapper::toFullDTO)
                .collect(toList());

        return new PageImpl<ProductFullDTO>(result, pageRequest, pageResult.getTotalElements());
    }

    public Page<Product> findAll(PageRequest pageRequest) {
        return this.dao.findAll(pageRequest);
    }

@Repository
public interface ProductRepository extends JpaRepository<Product, Integer>, JpaSpecificationExecutor<Product> {

    Page<Product> findAllByTypeIn(Pageable page, String... types);

    Page<Product> findAll(Pageable page);
}

The question is how to implement search functionality for this Page request?
I would like to send params like type and dateAdded into GET params and return filtered result?

回答

您可以通过多种方式实现所需的行为。

让我们暂时忘记分页参数。

首先,定义一个 POJO,将所需的不同字段作为搜索条件进行凝集。例如:

public class ProductFilter {
  private String type;
  private LocalDate dateAdded;

  // Setters and getters omitted for brevity
}

此信息应该是您的Controller搜索方法的入口点。

请尽管 a@GetMapping非常合适,但请考虑使用 a@PostMapping代替,主要是为了避免可能出现的URL 长度问题1

@PostMapping
public PageImpl<ProductFullDTO> list(ProductFilter filter) {
    //...
}

或者将您的搜索条件作为 JSON 负载并@RequestBody在您的控制器中使用:

@PostMapping
public PageImpl<ProductFullDTO> list(@RequestBody ProductFilter filter) {
    //...
}

现在,如何在Controller级别处理分页相关信息?您也有多种选择。

  • 您可以包括必要的字段,page并且size,在新的领域ProductFilter
public class ProductFilter {
  private String type;
  private LocalDate dateAdded;
  private int page;
  private int size;

  // Setters and getters omitted for brevity
}
  • 您可以创建一个公共 POJO 来处理分页字段并在您的过滤器中扩展它(也许您可以直接使用PageRequest它自己,尽管我考虑了一种更简单的方法来为此功能创建自己的 POJO 以保持独立于 Spring - 任何其他框架 - 尽可能多):
public class PagingForm {
  private int page;
  private int size;
  //...
}

public class ProductFilter extend PagingForm {
  private String type;
  private LocalDate dateAdded;

  // Setters and getters omitted for brevity
}
  • 您可以(这是我的首选)按原样维护过滤器,并修改 url 以包含分页信息。如果您使用@RequestBody.

让我们考虑这种方法来继续对服务层进行必要的更改。请看相关代码,注意内联注释:

@PostMapping
public PageImpl<ProductFullDTO> list(
  @RequestParam(name = "page", defaultValue = "0") int page,
  @RequestParam(name = "size", defaultValue = "10") int size,
  @RequestBody ProductFilter filter
) {
    PageRequest pageRequest = PageRequest.of(page, size);
    // Include your filter information
    PageImpl<ProductFullDTO> result = productRestService.page(filter, pageRequest);
    return result;
}

您的page方法可能如下所示2

public PageImpl<ProductFullDTO> page(final ProductFilter filter, final PageRequest pageRequest){
  // As far as your repository extends JpaSpecificationExecutor, my advice
  // will be to create a new Specification with the appropriate filter criteria
  // In addition to precisely provide the applicable predicates, 
  // it will allow you to control a lot of more things, like fetch join
  // entities if required, ...
  Specification<Product> specification = buildProductFilterSpecification(filter);
          
  // Use now the constructed specification to filter the actual results
  Page<Product> pageResult = productService.findAll(specification, pageRequest);
  List<ProductFullDTO> result = pageResult
                .stream()
                .map(productMapper::toFullDTO)
                .collect(toList());

  return new PageImpl<ProductFullDTO>(result, pageRequest, pageResult.getTotalElements());
}

您可以执行建议SpecificationProduct,因为你需要。一些一般提示:

  • 始终Specification在为任务定义的方法中的单独类中定义 ,这将允许您在代码的多个位置重用并有利于可测试性。
  • 如果您愿意,为了提高代码的易读性,您可以在定义时使用 lambda。
  • 要识别谓词构造中使用的不同字段,请始终使用元模型类而不是Strings 作为字段名称。您可以使用Hibernate Metamodel 生成器来生成必要的工件。
  • 在您的特定用例中,不要忘记包含必要的sort定义以提供一致的结果。

总之,buildProductFilterSpecification可以是这样的:

public static Specification<Product> buildProductFilterSpecification(final ProjectFilter filter) {
  return (root, query, cb) -> {

    final List<Predicate> predicates = new ArrayList<>();

    final String type = filter.getType();
    if (StringUtils.isNotEmpty(type)) {
      // Consider the use of like on in instead
      predicates.add(cb.equal(root.get(Product_.type), cb.literal(type)));
    }

    // Instead of dateAdded, please, consider a date range, it is more useful
    // Let's suppose that it is the case
    final LocalDate dateAddedFrom = filter.getDateAddedFrom();
    if (dateAddedFrom != null){
      // Always, specially with dates, use cb.literal to avoid underlying problems    
      predicates.add(
        cb.greaterThanOrEqualTo(root.get(Product_.dateAdded), cb.literal(dateAddedFrom))
      );
    }

    final LocalDate dateAddedTo = filter.getDateAddedTo();
    if (dateAddedTo != null){
      predicates.add(
        cb.lessThanOrEqualTo(root.get(Product_.dateAdded), cb.literal(dateAddedTo))
      );
    }

    // Indicate your sort criteria
    query.orderBy(cb.desc(root.get(Product_.dateAdded)));

    final Predicate predicate = cb.and(predicates.toArray(new Predicate[predicates.size()]));

    return predicate;
  };
}

1正如@blagerweij 在他的评论中指出的那样,使用POST而不是GET会以某种方式阻止在 HTTP(Web 服务器,Spring MVC)级别使用缓存。

尽管如此,这里有必要指出两件重要的事情:

  • 一,您可以安全地使用 aGETPOSTHTTP 动词来处理您的搜索,所提供的解决方案将对这两个动词都有效,只需进行最少的修改。
  • 二、使用一种或其他 HTTP 方法将高度依赖于您的实际用例:
    • 例如,如果您正在处理大量参数,那么如果您使用GET动词,则 URL 限制可能是一个问题。我自己多次遇到这个问题。
    • 如果情况并非如此,并且您的应用程序主要是分析性的,或者至少您正在处理不经常更改的静态信息或数据,那么使用GETHTTP 级缓存可以为您提供很大的好处。
    • 如果您的信息主要是可操作的,并且有很多变化,您可以始终依赖服务器端缓存,在数据库或服务层级别,使用 Redis、Caffeine 等,提供功能性缓存。这种方法通常会为您提供关于缓存逐出和一般缓存管理的更细粒度的控制。

2 @blagerweij 在他的评论中也建议使用Slice。如果您不需要知道记录集的元素总数 - 例如,在滚动页面并以固定数量触发新记录集的获取的典型用例中,将显示在页面中 - 使用Slice而不是Page可以为您提供巨大的性能优势。例如,请考虑查看这个SO 问题。

在典型的用例中,为了SlicefindAll您的存储库一起使用,无法扩展,JpaRepository因为它反过来扩展PagingAndSortingRepository并且该接口已经提供了您现在正在使用的方法,findAll(Pageable pageable).

可能您可以CrudRepository改为expend并定义类似于以下内容的方法:

Slice<Product> findAll(Pageable pageable);

但是,我不确定您是否可以将Slices 与 s 一起使用Specification:请参阅此 Github 问题:恐怕它仍然是 WIP。


以上是ImplementSearchspecificationforpagination的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>