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

Das*_*Boy 5 java predicate spring-data-jpa spring-boot

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

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

该解决方案有效,但面试官表示该解决方案具有可扩展性和可维护性。我应该使用谓词来实现它。有人可以向我展示一个示例,我如何使用更易于维护的谓词来实现此类功能?一些带有代码的示例将不胜感激。

我认为我很聪明,通过检查参数是否为空来满足可选参数和在单个调用中找到的记录。我想到的另一个与此相关的问题是,从 DB 获取所有记录然后使用谓词对其进行过滤真的是一个好习惯吗?还有当我们涉及多个对象/实体时如何过滤,可以为单个类型创建谓词。

@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);
Run Code Online (Sandbox Code Playgroud)

g00*_*00b 8

Spring 对 JPA 标准 API(使用谓词)有一个包装器,称为规范 API。

编写规范时您可以执行以下操作,为每个标准编写规范:

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);
}
Run Code Online (Sandbox Code Playgroud)

它还允许您编写连接操作:

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);
    };
}
Run Code Online (Sandbox Code Playgroud)

您可以null在一个条件内返回,这允许您将这些规范设为“可选”。之后,您可以使用Specifications.where()组合这些条件:

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

如果你像我在这个例子中所做的那样编写单独的规范,你可以在必要时重新使用它们。否则,您将不得不编写特定于操作的规范,而面试官也可能不会发现它具有可扩展性。

编写规范后,您必须从JpaSpecificationExecutor接口扩展您的存储库并使用该findAll(Specification)方法。