使用 Java 8 个谓词的 JPA 存储库过滤器
JPA Repository filter using Java 8 Predicates
我在使用 Spring Boot 的一个面试测试中有一个要求,我必须创建一个端点来接受一堆可选的请求参数,然后 returns 一个基于汽车的列表这些参数,如汽车型号、车牌、发动机类型、制造商、driver、租给的公司等。而汽车、driver 和制造商都是独立的实体。
我在 JPARepository 中使用单个 JPQL 查询实现此功能,该查询实现 LEFT JOINS 并在 where 子句中进行过滤,例如 licensePlate = licensePlateParameter OR licensePlatParameter is null 等
该解决方案有效,但面试官表示该解决方案具有可扩展性和可维护性。我应该使用谓词来实现它。有人可以给我举个例子,我如何使用更易于维护的谓词来实现这样的功能?一些带有代码的示例将不胜感激。
我认为我很聪明,通过检查参数是否为 null 来满足可选参数的需求,并在一次调用中找到记录。我想到的另一个与此相关的问题是从数据库中获取所有记录然后使用谓词过滤它真的是一个好习惯吗?还有当涉及多个 objects/entities 时我们如何过滤,可以为单个类型创建谓词。
@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);
您可以使用 Criteria Api 而不是 JPQL。
例子参考例子1
https://www.programcreek.com/java-api-examples/index.php?api=javax.persistence.criteria.Predicate
您可以在 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()]));
}
});
}
为此,您需要在您的存储库中实施 JpaSpecificationExecutor
。
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);
}
它还允许您编写连接操作:
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);
};
}
您可以 return null
在一个标准内,这允许您制定这些规范 "optional"。之后,您可以使用 Specifications.where()
组合这些条件:
Specification<Car> spec = Specifications
.where(withLicensePlate(licensePlate))
.and(withRating(rating))
.and(withName(name))
.and(withSeatType(seatType));
如果您像我在本例中那样编写单独的规范,则可以在必要时重复使用它们。否则,您将不得不编写特定于操作的规范,而面试官可能也不会发现可扩展性。
编写规范后,您必须从 JpaSpecificationExecutor
接口扩展您的存储库并使用 findAll(Specification)
方法。
我在使用 Spring Boot 的一个面试测试中有一个要求,我必须创建一个端点来接受一堆可选的请求参数,然后 returns 一个基于汽车的列表这些参数,如汽车型号、车牌、发动机类型、制造商、driver、租给的公司等。而汽车、driver 和制造商都是独立的实体。
我在 JPARepository 中使用单个 JPQL 查询实现此功能,该查询实现 LEFT JOINS 并在 where 子句中进行过滤,例如 licensePlate = licensePlateParameter OR licensePlatParameter is null 等
该解决方案有效,但面试官表示该解决方案具有可扩展性和可维护性。我应该使用谓词来实现它。有人可以给我举个例子,我如何使用更易于维护的谓词来实现这样的功能?一些带有代码的示例将不胜感激。
我认为我很聪明,通过检查参数是否为 null 来满足可选参数的需求,并在一次调用中找到记录。我想到的另一个与此相关的问题是从数据库中获取所有记录然后使用谓词过滤它真的是一个好习惯吗?还有当涉及多个 objects/entities 时我们如何过滤,可以为单个类型创建谓词。
@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);
您可以使用 Criteria Api 而不是 JPQL。
例子参考例子1
https://www.programcreek.com/java-api-examples/index.php?api=javax.persistence.criteria.Predicate
您可以在 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()]));
}
});
}
为此,您需要在您的存储库中实施 JpaSpecificationExecutor
。
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);
}
它还允许您编写连接操作:
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);
};
}
您可以 return null
在一个标准内,这允许您制定这些规范 "optional"。之后,您可以使用 Specifications.where()
组合这些条件:
Specification<Car> spec = Specifications
.where(withLicensePlate(licensePlate))
.and(withRating(rating))
.and(withName(name))
.and(withSeatType(seatType));
如果您像我在本例中那样编写单独的规范,则可以在必要时重复使用它们。否则,您将不得不编写特定于操作的规范,而面试官可能也不会发现可扩展性。
编写规范后,您必须从 JpaSpecificationExecutor
接口扩展您的存储库并使用 findAll(Specification)
方法。