Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA Repository filter using Java 8 Predicates

I had a requirement in one of my interview tests using Spring Boot where I had to create an endpoint that accepts a bunch of optional request params and then returns a list of cars based on these parameters like car model, license plate, engine type, manufacturer, driver, the company it was rent to etc etc. And car, driver and manufacturer are all separate entities.

I implement this functionality in JPARepository with a single JPQL query implementing LEFT JOINS and filter in where clause like licensePlate = licensePlateParameter OR licensePlatParameter is null etc.

The solution was working however the interviewer said the solution was scalable and maintainable. I should have implemented it using predicates. Can someone show me an example how could I implement such functionality using predicates that is easier to maintain? Some examples with code would be greatly appreciated.

I thought I was smart by catering both the optional parameters and found records with in a single call by checking if the parameter is null or not. Another question related to that I have in mind is it really a good practice to get all the records from DB and then filter it using predicates? Also how to we filter when we have multiple objects/entities involved, predicates can be created for a single type.

@Query("SELECT d FROM Driver d LEFT JOIN d.car c WHERE (d.name = :name OR :name is null) "
            + "and (c.licensePlate = :licensePlate OR :licensePlate is null) "
            + "and (c.rating = :rating OR :rating is null) " and so on

    List<Driver> findByAttributes(@Param("name") String name, 
            @Param("licensePlate") String licensePlate,
            @Param("rating") Integer rating,
            and so on);
like image 756
Dashing Boy Avatar asked Sep 11 '18 16:09

Dashing Boy


2 Answers

You can use the dynamic query in Spring JPA as like this:

public List<Employee> findByCriteria(String employeeName,String employeeRole){
       return employeeDAO.findAll(new Specification<Employee>() {
           @Override
           public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
               List<Predicate> predicates = new ArrayList<>();
               if(employeeName!=null) {
                   predicates.add(criteriaBuilder.and(criteriaBuilder.like(root.get("employeeName"), "%"+employeeName+"%")));
               }
               if(employeeRole!=null){
                   predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("employeeRole"), employeeRole)));
               }
               return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
           }
       });
   }

For this you need to implement JpaSpecificationExecutor in your repository.

Here is detail explanation for Spring JPA dynamic query

like image 32
subhash lamba Avatar answered Oct 06 '22 01:10

subhash lamba


Spring has a wrapper around the JPA criteria API (that uses predicates) and is called the specification API.

What you can do when writing specifications is the following, write a specification for each criteria:

public static Specification<Car> withLicensePlate(String licensePlate) {
    return (root, query, cb) -> licensePlate == null ? null : cb.equal(root.get("licensePlate"), licensePlate);
}

public static Specification<Car> withRating(String rating) {
    return (root, query, cb) -> rating == null ? null : cb.equal(root.get("rating"), rating);
}

public static Specification<Car> withName(String name) {
    return (root, query, cb) -> name == null ? null : cb.equal(root.get("name"), name);
}

it also allows you to write a join operation as well:

public static Specification<Car> withSeatType(String type) {
    return (root, query, cb) -> {
        return type == null ? null : cb.equal(root.join("interior", JoinType.LEFT).get("type"), type);
    };
}

You can return null within a criteria, which allows you to make these specifications "optional". After that, you can use Specifications.where() to combine these criteria:

 Specification<Car> spec = Specifications
      .where(withLicensePlate(licensePlate))
      .and(withRating(rating))
      .and(withName(name))
      .and(withSeatType(seatType));

If you write separate specifications like I did in this example, you can re-use them where necessary. Otherwise, you'll have to write operation-specific specifications, and the interviewer might not find that scalable either.

After writing the specifications, you have to extend your repository from the JpaSpecificationExecutor interface and use the findAll(Specification) method.

like image 104
g00glen00b Avatar answered Oct 06 '22 00:10

g00glen00b