使用 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<Some>
)。
之后,您必须创建三个规范(每列一个),在the Spring docs 他们将这些规范定义为类中的静态方法。基本上,创建规范意味着您必须继承 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));
【讨论】:
我使用哪个 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 规范进行多列搜索的主要内容,如果未能解决你的问题,请参考以下文章
使用连接表存储库的@manytomany 中的 Spring 数据 jpa 规范和可分页