• 在 Spring Data应用程序中使用Criteria条件查询


    1. 简介

    Spring Data JPA提供了许多处理实体的方法,包括查询方法和自定义JPQL查询。但有时,我们需要一种更具程序化的方法,例如标准APIQueryDSL

    条件 API 提供了一种创建类型化查询的编程方式,这有助于我们避免语法错误。此外,当我们将其与元模型 API 一起使用时,它会进行编译时检查以确认我们是否使用了正确的字段名称和类型。

    但是,它有其缺点;我们必须用样板代码编写冗长的逻辑。

    在本教程中,我们将学习如何使用条件查询实现我们的自定义 DAO 逻辑。我们还将说明 Spring 如何帮助减少样板代码。

    2. 示例应用程序

    为了示例中的简单起见,我们将以多种方式实现相同的查询:按作者姓名和包含String 的标题查找书籍。

    下面是Book实体:

    1. @Entity
    2. class Book {
    3. @Id
    4. Long id;
    5. String title;
    6. String author;
    7. // getters and setters
    8. }

    因为我们想让事情变得简单,所以我们在本教程中不会使用元模型 API。

    3.@Repository

    众所周知,在 Spring 组件模型中,我们应该将数据访问逻辑放在 @Repositorybean 中。当然,此逻辑可以使用任何实现,例如条件 API。

    为此,我们只需要一个EntityManager实例,我们可以自动连线:

    1. @Repository
    2. class BookDao {
    3. EntityManager em;
    4. // constructor
    5. List findBooksByAuthorNameAndTitle(String authorName, String title) {
    6. CriteriaBuilder cb = em.getCriteriaBuilder();
    7. CriteriaQuery cq = cb.createQuery(Book.class);
    8. Root book = cq.from(Book.class);
    9. Predicate authorNamePredicate = cb.equal(book.get("author"), authorName);
    10. Predicate titlePredicate = cb.like(book.get("title"), "%" + title + "%");
    11. cq.where(authorNamePredicate, titlePredicate);
    12. TypedQuery query = em.createQuery(cq);
    13. return query.getResultList();
    14. }
    15. }

    上面的代码遵循标准条件 API 工作流:

    • 首先,我们得到一个CriteriaBuilder引用,我们可以使用它来创建查询的不同部分。
    • 使用CriteriaBuilder,我们创建了一个CriteriaQuery,它描述了我们要在查询中执行的操作。它还声明结果中行的类型。
    • 使用CriteriaQuery我们声明查询的起点(Book实体),并将其存储在book变量中供以后使用。
    • 接下来,使用CriteriaBuilder,我们针对Book实体创建谓词。请注意,这些谓词尚无任何效果。
    • 我们将这两个谓词应用于我们的CriteriaQuery。CriteriaQuery.where(谓词...)将其参数组合在一个逻辑.这就是我们将这些谓词与查询绑定时的重点。
    • 之后,我们从CriteriaQuery 创建一个TypedQuery 实例。
    • 最后,我们返回所有匹配的 Book实体。

    请注意,由于我们用 @Repository 标记了 DAO 类,Spring 为该类启用了异常转换

    4. 使用自定义方法扩展存储库

    拥有自动自定义查询是一项强大的 Spring 数据功能。但是,有时我们需要更复杂的逻辑,而我们无法通过自动查询方法创建这些逻辑。

    我们可以在单独的 DAO 类中实现这些查询(如上一节所述)。

    或者,如果我们希望一个@Repository接口有一个带有自定义实现的方法,我们可以使用可组合的存储库

    自定义界面如下所示:

    1. interface BookRepositoryCustom {
    2. List findBooksByAuthorNameAndTitle(String authorName, String title);
    3. }

    这是@Repository界面:

    interface BookRepository extends JpaRepository, BookRepositoryCustom {}

    我们还必须修改我们之前的 DAO 类来实现BookRepositoryCustom,并将其重命名为BookRepositoryImpl

    1. @Repository
    2. class BookRepositoryImpl implements BookRepositoryCustom {
    3. EntityManager em;
    4. // constructor
    5. @Override
    6. List findBooksByAuthorNameAndTitle(String authorName, String title) {
    7. // implementation
    8. }
    9. }

    当我们将 BookRepository 声明为依赖项时,Spring 会找到BookRepositoryImpl并在调用自定义方法时使用它。

    假设我们要选择要在查询中使用的谓词。例如,当我们不想按作者和书名查找书籍时,我们只需要匹配作者即可。

    有多种方法可以做到这一点,例如仅在传递的参数不为 null 时才应用谓词:

    1. @Override
    2. List findBooksByAuthorNameAndTitle(String authorName, String title) {
    3. CriteriaBuilder cb = em.getCriteriaBuilder();
    4. CriteriaQuery cq = cb.createQuery(Book.class);
    5. Root book = cq.from(Book.class);
    6. List predicates = new ArrayList<>();
    7. if (authorName != null) {
    8. predicates.add(cb.equal(book.get("author"), authorName));
    9. }
    10. if (title != null) {
    11. predicates.add(cb.like(book.get("title"), "%" + title + "%"));
    12. }
    13. cq.where(predicates.toArray(new Predicate[0]));
    14. return em.createQuery(cq).getResultList();
    15. }

    但是,这种方法使代码难以维护,特别是如果我们有许多谓词并希望使它们成为可选的。

    将这些谓词外部化将是一个实用的解决方案。有了 JPA 规范,我们可以做到这一点,甚至更多。

    5. 使用 JPA 规范

    Spring Data 引入了org.springframework.data.jpa.domain.Specification接口来封装单个谓词:

    1. interface Specification {
    2. Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb);
    3. }

    我们可以提供创建规范实例的方法:

    1. static Specification hasAuthor(String author) {
    2. return (book, cq, cb) -> cb.equal(book.get("author"), author);
    3. }
    4. static Specification titleContains(String title) {
    5. return (book, cq, cb) -> cb.like(book.get("title"), "%" + title + "%");
    6. }

    要使用它们,我们需要我们的存储库来扩展org.springframework.data.jpa.repository.JpaSpecificationExecutor

    interface BookRepository extends JpaRepository, JpaSpecificationExecutor {}

    此接口声明了使用规范的便捷方法。例如,现在我们可以使用以下单行代码找到指定作者的所有Book实例:

    bookRepository.findAll(hasAuthor(author));

    不幸的是,我们没有得到任何可以传递多个规范参数的方法。相反,我们在org.springframework.data.jpa.domain.Specification接口中获取实用程序方法。

    例如,我们可以将两个规范实例与一个逻辑

    bookRepository.findAll(where(hasAuthor(author)).and(titleContains(title)));

    在上面的例子中,where()规范类的静态方法。

    这样我们就可以使查询模块化。此外,我们不必编写标准API样板,因为Spring为我们提供了它。

    请注意,这并不意味着我们不必再编写标准样板;这种方法只能处理我们看到的工作流,即选择满足所提供条件的实体。

    查询可以有许多它不支持的结构,包括分组、返回与我们选择的不同类或子查询。

    6. 结论

    在本文中,我们讨论了在 Spring 应用程序中使用条件查询的三种方法:

    • 创建 DAO 类是最直接、最灵活的方法。
    • 扩展@Repository接口,与自动查询无缝集成
    • 规范实例中使用谓词,使简单案例更简洁、不那么冗长

    像往常一样,这些示例可以在GitHub上找到。

  • 相关阅读:
    【ML】第二章 端到端机器学习项目
    Revit插件“有求必应”的【批量喷头】生成喷头
    压测的问题排查
    java开发之个微机器人的二次开发
    Java之消息推送浅入浅出
    路线中桩测量计算程序浅析
    计算机网络概述
    【机器学习】支持向量机【下】软间隔与核函数
    HarmonyOS应用开发Web组件基本属性应用和事件
    web前端之float布局与flex布局
  • 原文地址:https://blog.csdn.net/allway2/article/details/128119019