Skip to content
Advertisement

Implement Search specification for pagination

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?

Advertisement

Answer

There are two ways to achieve this:

  1. Examples. Simple but can only be used for simple searches
    @GetMapping("products")
    public Page<ProductFullDTO> findProducts(Product example,
Pageable pageable) {
   return productService.findProductsByExample(example, pageable);
}

To be able to search by example, you need make sure your ProductRepository interface extends from QueryByExampleExecutor:

public interface ProductRepository 
  extends JpaRepository<Product, Long>, QueryByExampleExecutor<Product> {}

In your service method, you can simply pass the example object:

public Page<ProductFullDTO> findProductsByExample(Product exampleProduct, Pageable pageable) {
    return productRepository.findByExample(Example.of(exampleProduct), pageable).map(ProductFullDTO::new);
}

See https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example.matchers for more information

  1. Using JPA Specification. This is useful in case the search parameters are more complex (e.g. when you need to join other entities)
    @GetMapping("products")
    public Page<ProductFullDTO> findProducts(@Valid ProductSearchParams params,
Pageable pageable) {
   return productService.findProducts(params, pageable);
}

The ProductSearchParams class could look like this:

public class ProductSearchParams {
   private String type;
   private Date dateAdded;
}

To use the search params, we need a Specification. To use this, you need make sure your ProductRepository interface extends from JpaSpecificationExecutor:

public interface ProductRepository 
  extends JpaRepository<Product, Long>, JpaSpecificationExecutor<Product> {}

Since the Specification interface has only one method, we can use a lambda in our service:

public Page<ProductFullDTO> findProducts(ProductSearchParams params, Pageable pageable) {
    Specification<Product> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (params.getType() != null) {
            predicates.add(cb.equal(root.get("type"), params.getType()));
        }
        if (params.getDateAdded() != null) {
            predicates.add(cb.greaterThan(root.get("dateAdded"), params.getDateAdded()));
        }
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    return productRepository.findAll(spec, pageable).map(ProductFullDTO::new);
}

Using Specification is more powerful, but also a bit more work. See https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#specifications for information on using Specifications. Using Specifications also allows you to write really elegant expressions, e.g. customerRepository.findAll( isLongTermCustomer().or(hasSalesOfMoreThan(amount)));

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement