JPA 规范示例

Posted

技术标签:

【中文标题】JPA 规范示例【英文标题】:JPA Specifications by Example 【发布时间】:2018-07-16 19:13:48 【问题描述】:

这里是 Spring Boot。当在实现复杂查询的上下文中使用时,我试图绕开JpaRepositoriesSpecifications 并努力在几个项目上看到“树林中的森林”。

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&lt;Person&gt;CriteriaQuery 代表什么?最重要的是,听起来您需要为要应用的每种类型的过滤器创建一个Specification impl,因为每个规范都会被翻译成一个且只有一个谓词...因此,例如,如果我想找到所有姓氏为“Smeeb”且年龄大于 25 岁的人,听起来我需要写一个 LastnameMatchingSpecification&lt;Person&gt; 和一个 AgeGreaterThanSpecification&lt;Person&gt;。有人可以为我确认或澄清这一点吗?!

【问题讨论】:

签出this answer 【参考方案1】:

Root&lt;Person&gt;CriteriaQuery 代表什么?

Root 是您查询的根,基本上是您要查询的什么。在Specification 中,您可以使用它对此做出动态反应。例如,这将允许您构建一个OlderThanSpecification 来处理带有modelYearCars 和带有dateOfBirthDrivers,方法是检测类型并使用适当的属性。

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

如果我想找到所有姓氏为“Smeeb”且年龄大于 25 岁的人,听起来我需要写一个 LastnameMatchingSpecification&lt;Person&gt; 和一个 AgeGreaterThanSpecification&lt;Person&gt;。有人可以为我确认或澄清这一点吗?!

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

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

【讨论】:

【参考方案2】:

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

想想这些对象:

    根是你的桌子。 CriteriaQuery 是您的查询,适用于应用不同的、子查询、排序依据等。 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 个字段的任意组合,并且此逻辑将动态构建查询以包含它们的条件。

例如 姓名 = 约翰和姓氏 = 多伊和年龄 = 41 要么 姓名 = 约翰和年龄 = 41 要么 姓名 = 约翰 等等

最后,在搜索字符串时,我建议使用 cb.like 而不是 cb.equal,这样您的搜索就可以通过用户或前端系统传递 % 进行部分搜索。

记住 cb.like 默认不区分大小写,它需要与 cb.lower 或 cb.upper 结合使用,例如:

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

希望这会有所帮助!

【讨论】:

【参考方案3】:

如果您还需要使用连接,则必须编写如下内容:

@Override public Predicate toPredicate(Root&ltOpportunity&gt root, CriteriaQuery&lt?&gt cq, CriteriaBuilder cb) Predicate predicate = cb.conjunction(); ... predicate.getExpressions().add(cb.equal(root.join("address").get("streetName"), person.getAddress().getStreetName())); ... return predicate;

【讨论】:

以上是关于JPA 规范示例的主要内容,如果未能解决你的问题,请参考以下文章

Spring Jpa 规范和渴望加载

Spring Data JPA - 规范和 Querydsl

Spring Boot中使用Spring Data JPA示例

如何将spring数据jpa规范查询中的distinct和sort与join结合起来

JPA

JPA规范及其它持久层框架