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:
- 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
- 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)));