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