使用 Spring JPA 规范进行多列搜索

Posted

技术标签:

【中文标题】使用 Spring JPA 规范进行多列搜索【英文标题】:Multi-Column Search with Spring JPA Specifications 【发布时间】:2018-04-08 19:15:22 【问题描述】:

我想在 Spring-Boot 后端创建多字段搜索。如何使用Specification<T> 做到这一点?

环境

Springboot
Hibernate
Gradle
Intellij

前端的 UI 是一个 Jquery 数据表。每列允许应用单个字符串搜索词。多个列中的搜索词由and 连接。

我已经将来自前端的过滤器填充到 Java 对象中。

第 1 步 扩展 JPA 规范执行器

public interface SomeRepository extends JpaRepository<Some, Long>, PagingAndSortingRepository<Some, Long>, JpaSpecificationExecutor 

第二步 创建一个新类 SomeSpec

这让我不知道代码是什么样子以及它是如何工作的。

我是否需要为每列设置一个方法? 什么是 Root,什么是 Criteria Builder? 还需要什么?

我是 JPA 的新手,所以虽然我不需要任何人为我编写代码,但详细的解释会很好。

更新 看来 QueryDSL 是解决这个问题的更简单和更好的方法。我正在使用 Gradle。我需要从this 更改我的build.gradle 吗?

【问题讨论】:

【参考方案1】:

如果您不想使用 QueryDSL,则必须编写自己的规范。首先,您需要像您一样从JpaSpecificationExecutor 扩展您的存储库。不过请确保添加泛型 (JpaSpecificationExecutor&lt;Some&gt;)。

之后,您必须创建三个规范(每列一个),在the Spring docs 他们将这些规范定义为类中的静态方法。基本上,创建规范意味着您必须继承 Specification&lt;Some&gt;,它只有一种方法可以实现,toPredicate(Root&lt;Some&gt;, CriteriaQuery&lt;?&gt;, CriteriaBuilder)

如果您使用的是 Java 8,则可以使用 lambdas 创建匿名内部类,例如:

 public class SomeSpecs 
     public static Specification<Some> withAddress(String address) 
          return (root, query, builder) -> 
               // ...
          ;
     
 

对于实际实现,您可以使用Root 来获取特定节点,例如。 root.get("address")。另一方面,CriteriaBuilder 是定义 where 子句,例如。 builder.equal(..., ...).

在你的情况下,你想要这样的东西:

 public class SomeSpecs 
     public static Specification<Some> withAddress(String address) 
          return (root, query, builder) -> builder.equal(root.get("address"), address);
     
 

或者,如果您想使用LIKE 查询,您可以使用:

public class SomeSpecs 
     public static Specification<Some> withAddress(String address) 
          return (root, query, builder) -> builder.like(root.get("address"), "%" + address + "%");
     
 

现在您必须对要过滤的其他字段重复此操作。之后,您必须一起使用所有规范(使用and()or(),...)。然后可以使用repository.findAll(Specification)方法根据该规范进行查询,例如:

public List<Some> getSome(String address, String name, Date date) 
    return repository.findAll(where(withAddress(address))
         .and(withName(name))
         .and(withDate(date));

您可以使用静态导入来导入withAddress()withName()withDate(),使其更易于阅读。 where() 方法也可以静态导入(来自Specification.where())。

请注意,上述方法可能需要调整,因为如果地址字段为null,您不希望对其进行过滤。您可以通过返回 null 来执行此操作,例如:

public List<Some> getSome(String address, String name, Date date) 
    return repository.findAll(where(address == null ? null : withAddress(address))
         .and(name == null ? null : withName(name))
         .and(date == null ? null : withDate(date));

【讨论】:

我使用哪个 querydsl? com.mysema.querydsl 还是 com.querydsl? 当然,但如果我确实想使用它? @g00glen00b - 你能在这里指导我吗:***.com/questions/57167639/…?【参考方案2】:

您可以考虑使用 Spring Data 对 QueryDSL 的支持,因为您无需编写大量代码即可获得很多收益,即您实际上不必编写规范。

查看此处了解概览:

https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

虽然这种方法真的很方便(你甚至不必 编写一行实现代码来获取查询 执行)它有两个缺点:第一,查询方法的数量 可能会因更大的应用程序而增长 - 这是第二个 point - 查询定义了一组固定的标准。为了避免这些 两个缺点,如果你能想出一套不是很酷 您可以动态组合以构建您的原子谓词 查询?

所以基本上你的存储库变成了:

public interface SomeRepository extends JpaRepository<Some, Long>,
     PagingAndSortingRepository<Some, Long>, QueryDslPredicateExecutor<Some>


您还可以获取自动绑定到 Controller 中的谓词的请求参数:

看这里:

https://spring.io/blog/2015/09/04/what-s-new-in-spring-data-release-gosling#querydsl-web-support

所以你的控制器看起来像:

  @Controller
  class SomeController 

    private final SomeRepository repository;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    String index(Model model,
                 @QuerydslPredicate(root = Some.class) Predicate predicate,
                 Pageable pageable) 

      model.addAttribute("data", repository.findAll(predicate, pageable));
      return "index";
    
  

因此,有了以上内容,只需在您的项目中启用 QueryDSL,UI 现在应该能够按各种条件组合过滤、排序和分页数据。

【讨论】:

我不确定是否需要围绕 QueryDSL 提出一个新问题,或者这里的答案是否可以调整? 如果它在 Gradle 周围,那么我不知道 Gradle,但您链接到的问题表明需要什么。这里也有一个插件:github.com/ewerk/gradle-plugins/tree/master/querydsl-plugin

以上是关于使用 Spring JPA 规范进行多列搜索的主要内容,如果未能解决你的问题,请参考以下文章

使用连接的 Spring Data JPA 规范的不同结果

Spring JPA配置讲解

使用Spring Data JPA进行搜索

使用连接表存储库的@manytomany 中的 Spring 数据 jpa 规范和可分页

处理 JPA 规范和 spring-data-jpa 时如何使用声明 Stream 作为返回类型

spring-data-jpa