如何为多个实体编写单个 jpa 规范

Posted

技术标签:

【中文标题】如何为多个实体编写单个 jpa 规范【英文标题】:how to write a single jpa specification for multiple entities 【发布时间】:2020-07-30 18:51:47 【问题描述】:

我正在开发一个 Spring Boot - 具有多个实体的应用程序,这些实体具有一些相同的过滤列。

目前,我在多个存储库中定义了相同的查询,所以在做了一些研究之后,我偶然发现了一篇关于 JPA 的文章 - 规范:https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

所以我创建了一个通用类来构建 JPA 规范:

public final class GenericSpecifications<T>

    public Specification whereNameLikeAndDateGreatherThan(String fieldName, String fieldDate, String name, LocalDate date)
    
       return (root, query, builder) -> builder.lessThan(root.get(columnName), date);
    

所以在服务中我可以使用:

repository.findAll(whereNameLikeAndDateGreatherThan(Person_.name, Person_.date, "Max", LocalDate.now());

这样,我在一个中心位置有一个查询/规范,我不需要在所有存储库上编写/维护相同的查询。

但是,我有更复杂的查询,需要过滤多个列。 这意味着我的 GenericSpecification-Class 中的方法变得过于臃肿,因为我需要传递多个列名和搜索值,所以我最终可能会得到带有 6 个或更多参数的方法。

我可以定义一个由所有其他实体扩展的抽象实体类。这个抽象实体将具有所有公共字段,以确保所有实体具有相同的列。 然后我可以使用这些名称进行过滤,因此我根本不必传递字段/列名称。

但是,我不确定这是否是解决我的问题的最干净的方法。 你知道是否有更好的方法来做到这一点?

【问题讨论】:

创建一个更高级别的构建器类来保留根和查询参数,并向单个规范添加额外的过滤器,然后在构建器完成后返回。 @daniu thx 回复,但您能否详细说明您的建议,因为我不确定这如何解决我的问题? 【参考方案1】:

我认为最简洁的方法是使用继承,但在规范创建者中,而不是实体中。所以例如类似的东西(如果它编译没有尝试,所以它可能没有,但应该给出这个想法):

class BasicSpecificationBuilder<T> 
    public Specification<T> stringEqual(String fieldName, String value) 
        // root is Root<T> here, don't know if this needs to be specified
        return (root, query, builder) -> 
                builder.equal(root.<String>get(fieldName), value);
        
    
    public Specification<T> dateAfter(String fieldName, LocalDate value) 
        return (root, query, builder) ->
                builder.<LocalDate>greaterThan(root.get(fieldName), value);
    

// extend per entity type and required queries
class ContractSpecificationBuilder<Contract> extends BasicSpecificationBuilder<Contract> 
    public Specification<Contract> contractsCreatedAfter(String partner, LocalDate date) 
        return (root, query, builder) -> 
            stringEqual(Contract_.partnerName, partner)
                .and(
            dateAfter(Contract_.closeDate, date));
    

class EmployeeSpecificationBuilder<Employee> extends BasicSpecificationBuilder<Employee> 
    public Specification<Employee> employeesJoinedAfter(String name, LocalDate date) 
        return (root, query, builder) ->
            stringEqual(Employee_.name, name)
                .and(
            dateAfter(Employee_.entryDate, date));
    

通过这种方式,您可以在基类中拥有一组可以重用的构建器方法,以及不会爆炸的查询,因为它们是按实体分开的。如上例所示,可能存在一些代码重复 - 如果重复代码过多,您可以将这些常见组合重构到基类中。

class BasicSpecificationBuilder<T> 
    public Specification<T> stringEqualAndDateAfter(String stringField, String stringValue, String dateField, LocalDate dateValue) 
    public Specification<Employee> employeesJoinedAfter(String name, LocalDate date) 
        return (root, query, builder) ->
            stringEqual(stringField, name)
                .and(
            dateAfter(dateField, date));
    

class ContractSpecificationBuilder<Contract> extends BasicSpecificationBuilder<Contract> 
    public Specification<Contract> contractsCreatedAfter(String partner, LocalDate date) 
        return stringEqualAndDateAfter(Contract_.partnerName, partner, Contract_.closeDate, date);
    

这是品味和代码质量设置的问题(我们在 SonarQube 中进行了代码重复测量,但我认为这不会超过限制)。

由于这些都是工厂方法,您可以对提供静态方法的类和包含作为静态实用程序方法的基本方法的“基”类执行几乎相同的操作。不过,我有点不喜欢通用静态方法的语法。

假设您阅读了Baeldung intro on how to use Specification 并且不喜欢这种方法。

【讨论】:

以上是关于如何为多个实体编写单个 jpa 规范的主要内容,如果未能解决你的问题,请参考以下文章

如何为以下语句编写标准构建器 API JPA 查询

在 Spring Data JPA 中连接两个表实体

将多个表动态映射到单个实体

如何为 JPA 和 Hibernate 创建一个 persistence.xml 文件?

使用jpa存储库查询多个表

JPA:返回多个实体的查询