3

我想在 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 {

Step2 创建一个新类 SomeSpec

这就是我对代码的外观和工作方式感到迷茫的地方。

每列都需要一个方法吗?什么是 Root,什么是 Criteria Builder?还需要什么?

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

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

4

2 回答 2

2

如果您不想使用 QueryDSL,则必须编写自己的规范。首先,您需要JpaSpecificationExecutor像以前一样扩展您的存储库。确保添加泛型虽然 ( JpaSpecificationExecutor<Some>)。

之后,您必须创建三个规范(每列一个),在Spring 文档中,他们将这些规范定义为类中的静态方法。基本上,创建规范意味着您必须对Specification<Some>只有一种方法要实现的toPredicate(Root<Some>, CriteriaQuery<?>, 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));
}
于 2017-10-27T09:18:03.530 回答
2

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

请参阅此处了解概述:

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

尽管这种方法非常方便(您甚至不必编写一行实现代码来执行查询),但它有两个缺点:首先,对于较大的应用程序,查询方法的数量可能会增加,因为 - 这就是第二点 -查询定义了一组固定的标准。为了避免这两个缺点,如果您可以提出一组可以动态组合以构建查询的原子谓词,那不是很酷吗?

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

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 现在应该能够通过各种标准组合过滤、排序和分页数据。

于 2017-10-27T08:50:30.880 回答