JPA 规范示例

JPA Specifications by Example

Spring 在这里开机。当在实现复杂查询的上下文中使用时,我试图围绕 JpaRepositoriesSpecifications 进行思考,并且很难在多个项目上看到 "forest through the trees"。

一个Specification的典型例子如下:

public class PersonSpecification implements Specification<Person> {
    private Person filter;

    public PersonSpecification(Person filter) {
        super();
        this.filter = filter;
    }

    public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> cq,
            CriteriaBuilder cb) {
        Predicate p = cb.disjunction();

        if (filter.getName() != null) {
            p.getExpressions()
                    .add(cb.equal(root.get("name"), filter.getName()));
        }

        if (filter.getSurname() != null && filter.getAge() != null) {
            p.getExpressions().add(
                    cb.and(cb.equal(root.get("surname"), filter.getSurname()),
                            cb.equal(root.get("age"), filter.getAge())));
        }

        return p;
    }
}

在这个toPredicate(...)方法中,Root<Person>CriteriaQuery代表什么?最重要的是,它 听起来 你需要为你想要应用的 each 类型的过滤器创建一个 Specification impl,因为每个规范被翻译成一个且只有一个谓词...所以例如,如果我想找到所有姓氏为 "Smeeb" 且年龄大于 25 岁的人,听起来我需要写一个 LastnameMatchingSpecification<Person> 以及 AgeGreaterThanSpecification<Person>。有人可以为我确认或澄清这一点吗?!

what do the Root<Person> and CriteriaQuery represent?

Root 是查询的根,基本上是 What 您要查询的内容。在 Specification 中,您可以使用它对此做出动态反应。例如,这将允许您构建一个 OlderThanSpecification 以通过检测类型和使用适当的 属性.

类似 CriteriaQuery 是完整的查询,您可以再次使用它来检查它并根据它调整您正在构建的谓词。

if I wanted to find all people with a surname of "Smeeb" and an age greater than 25, it sounds like I would need to write a LastnameMatchingSpecification<Person> as well as an AgeGreaterThanSpecification<Person>. Can someone confirm or clarify this for me?!

我认为你错了。接受 Specification 的 Spring 数据接口只接受一个 Specification。因此,如果您想找到所有具有特定名称和特定年龄的 Person,您可以创建一个 Specification。类似于您引用的示例,它也结合了两个约束。

但是您可以创建单独的 Specification,然后创建另一个组合它们,如果您想单独使用每个,但也可以组合使用。

一开始这对我来说也很难,但现在我可以轻松地进行动态查询,并且每个 Table(当需要高级搜索时)有一个规范

将这些对象想象成:

  1. 根是你的table。
  2. CriteriaQuery 是您的查询,适用于应用 distinct、子查询、order by 等
  3. CriteriaBuilder 是您的条件,非常适合创建您的 where 子句

--

始终从列表开始,然后根据您的需要使用 AND/OR 条件在最后压缩它们。

public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
    List<Predicate> predicates = new ArrayList<>();

    if(filter.getName() != null) {
        predicates.add(cb.equal(root.get("name"), filter.getName());
    }
    if(filter.getSurname() != null) {
        predicates.add(cb.equal(root.get("surname"), filter.getSurname());
    }
    if(filter.getAge() != null) {
        predicates.add(cb.equal(root.get("age"), filter.getAge());
    }
    if(predicates.isEmpty()){
        predicates.add(cb.equal(root.get("id"), -1);
        /* 
         I like to add this because without it if no criteria is specified then 
         everything is returned. Because that's how queries work without where 
         clauses. However, if my user doesn't provide any criteria I want to 
         say no results found. 
        */
    }

    return query.where(cb.and(predicates.toArray(new Predicate[0])))
                .distinct(true).orderBy(cb.desc(root.get("name")).getRestriction();
}

现在我的用户可以在此处传递这 3 个字段的任意组合,此逻辑将动态构建查询以包含它们的条件。

例如 姓名 = John 姓氏 = Doe 年龄 = 41 要么 姓名 = 约翰,年龄 = 41 要么 名字 = 约翰 等等

最后,在搜索字符串时,我建议使用 cb.like 而不是 cb.equal,这样您的搜索就可以使用 % is passed by user 或 frontend system 进行部分搜索。

记住cb.like默认不区分大小写需要和cb.lower或cb.upper一起使用如:

 predicates.add(cb.like(cb.lower(root.get("name"), filter.getName().toLowercase());

希望对您有所帮助!

如果您还需要使用联接,则必须这样写:



    @Override
    public Predicate toPredicate(Root<Opportunity> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
        Predicate predicate = cb.conjunction();
        ...
        predicate.getExpressions().add(cb.equal(root.join("address").get("streetName"), person.getAddress().getStreetName()));
        ...
        return predicate;
    }