简体   繁体   English

使用 Java 8 谓词的 JPA 存储库过滤器

[英]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.我在使用 Spring Boot 的一次面试测试中有一个要求,我必须创建一个端点,该端点接受一堆可选的请求参数,然后根据这些参数返回汽车列表,例如汽车型号、车牌、发动机类型、制造商,司机,公司等。汽车,司机和制造商都是独立的实体。

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.我在 JPARepository 中使用单个 JPQL 查询实现了这个功能,该查询实现了 LEFT JOINS 并在 where 子句中过滤,如 licensePlate = licensePlateParameter OR licensePlatParameter is null 等。

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?我想到的另一个与此相关的问题是,从 DB 获取所有记录然后使用谓词对其进行过滤真的是一个好习惯吗? 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);

Spring has a wrapper around the JPA criteria API (that uses predicates) and is called the specification API. Spring 对 JPA 标准 API(使用谓词)有一个包装器,称为规范 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".您可以在条件内返回null ,这允许您将这些规范设为“可选”。 After that, you can use Specifications.where() to combine these criteria:之后,您可以使用Specifications.where()组合这些条件:

 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.编写规范后,您必须从JpaSpecificationExecutor接口扩展您的存储库并使用findAll(Specification)方法。

You can use the dynamic query in Spring JPA as like this:您可以像这样在 Spring JPA 中使用动态查询:

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.为此,您需要在存储库中实现JpaSpecificationExecutor

Here is detail explanation for Spring JPA dynamic query 这是Spring JPA动态查询的详细解释

You could use Criteria Api instead JPQL.您可以使用 Criteria Api 代替 JPQL。

For example refer to the example 1例如参考例子1

https://www.programcreek.com/java-api-examples/index.php?api=javax.persistence.criteria.Predicate https://www.programcreek.com/java-api-examples/index.php?api=javax.persistence.criteria.Predicate

How are the Car and Driver entities are mapped?? Car和Driver实体是如何映射的? @Dashing Boy @短跑男孩

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM