Use Criteria Queries in a Spring Data Application – 在Spring数据应用中使用标准查询

最后修改: 2018年 8月 30日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Spring Data JPA provides many ways to deal with entities, including query methods and custom JPQL queries. But sometimes, we need a more programmatic approach, such as Criteria API or QueryDSL.

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

Criteria API offers a programmatic way to create typed queries, which helps us avoid syntax errors. Furthermore, when we use it with Metamodel API, it makes compile-time-checks to confirm if we used the correct field names and types.

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

However, it has its downsides; we have to write verbose logic bloated with boilerplate code.

然而,它也有缺点;我们必须写出冗长的逻辑,用模板代码来充实。

In this tutorial, we’ll learn how to implement our custom DAO logic using criteria queries. We’ll also illustrate how Spring helps to reduce boilerplate code. 

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

2. Sample Application

2.申请书样本

For the sake of simplicity in the examples, we’ll implement the same query in multiple ways: finding books by the name of the author and the title containing a String.

为了简化例子,我们将以多种方式实现同一个查询:通过作者的名字和包含String的标题寻找书籍。

Here’s the Book entity:

这里是实体。

@Entity
class Book {

    @Id
    Long id;
    String title;
    String author;

    // getters and setters

}

Because we want to keep things simple, we won’t use Metamodel API in this tutorial.

因为我们想保持简单,所以在本教程中我们将不使用Metamodel API。

3. @Repository Class

3.@Repository

As we know, in the Spring component model, we should place our data access logic in @Repository beans. Of course, this logic can use any implementation, like Criteria API.

我们知道,在Spring组件模型中,我们应该将数据访问逻辑放在@Repository Bean中。当然,这个逻辑可以使用任何实现,比如Criteria API。

To do this, we only need an EntityManager instance, which we can autowire:

要做到这一点,我们只需要一个EntityManager实例,我们可以自动连接它。

@Repository
class BookDao {

    EntityManager em;

    // constructor

    List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Book> cq = cb.createQuery(Book.class);

        Root<Book> book = cq.from(Book.class);
        Predicate authorNamePredicate = cb.equal(book.get("author"), authorName);
        Predicate titlePredicate = cb.like(book.get("title"), "%" + title + "%");
        cq.where(authorNamePredicate, titlePredicate);

        TypedQuery<Book> query = em.createQuery(cq);
        return query.getResultList();
    }

}

The code above follows a standard Criteria API workflow:

上面的代码遵循标准的Criteria API工作流程。

  • First, we get a CriteriaBuilder reference, which we can use to create different parts of the query.
  • Using the CriteriaBuilder, we create a CriteriaQuery<Book>, which describes what we want to do in the query. It also declares the type of a row in the result.
  • With CriteriaQuery<Book>, we declare the starting point of the query (Book entity), and store it in the book variable for later use.
  • Next, with CriteriaBuilder, we create predicates against our Book entity. Note that these predicates don’t have any effect yet.
  • We apply both predicates to our CriteriaQuery. CriteriaQuery.where(Predicate…) combines its arguments in a logical and. This is the point when we tie these predicates to the query.
  • After that, we create a TypedQuery<Book> instance from our CriteriaQuery.
  • Finally, we return all matching Book entities.

Note that since we marked the DAO class with @Repository, Spring enables exception translation for this class.

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

4. Extending Repository With Custom Methods

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

Having automatic custom queries is a powerful Spring Data feature. However, sometimes we need more sophisticated logic, which we can’t create with automatic query methods.

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

We can implement these queries in separate DAO classes (like in the previous section).

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

Or, if we want a @Repository interface to have a method with a custom implementation, we can use composable repositories.

或者,如果我们想让@Repository接口有一个具有自定义实现的方法,我们可以使用composable repositories

The custom interface looks like this:

自定义界面看起来像这样。

interface BookRepositoryCustom {
    List<Book> findBooksByAuthorNameAndTitle(String authorName, String title);
}

And here’s the @Repository interface:

这里是@Repository接口。

interface BookRepository extends JpaRepository<Book, Long>, BookRepositoryCustom {}

We also have to modify our previous DAO class to implement BookRepositoryCustom, and rename it to BookRepositoryImpl:

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

@Repository
class BookRepositoryImpl implements BookRepositoryCustom {

    EntityManager em;

    // constructor

    @Override
    List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
        // implementation
    }

}

When we declare BookRepository as a dependency, Spring finds BookRepositoryImpl and uses it when we invoke the custom methods.

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

Let’s say we want to select which predicates to use in our query. For example, when we don’t want to find the books by author and title, we only need the author to match.

比方说,我们想选择在查询中使用哪些谓词。例如,当我们不想按作者和书名查找书籍时,我们只需要作者的匹配。

There are multiple ways to do this, like applying a predicate only if the passed argument isn’t null:

有多种方法可以做到这一点,比如只在传递的参数不是null的情况下应用谓词。

@Override
List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Book> cq = cb.createQuery(Book.class);

    Root<Book> book = cq.from(Book.class);
    List<Predicate> predicates = new ArrayList<>();
    
    if (authorName != null) {
        predicates.add(cb.equal(book.get("author"), authorName));
    }
    if (title != null) {
        predicates.add(cb.like(book.get("title"), "%" + title + "%"));
    }
    cq.where(predicates.toArray(new Predicate[0]));

    return em.createQuery(cq).getResultList();
}

However, this approach makes the code hard to maintain, especially if we have many predicates and want to make them optional.

然而,这种方法使代码难以维护,尤其是当我们有许多谓词并想让它们变得可有可无。

It would be a practical solution to externalize these predicates. With JPA specifications, we can do exactly that, and much more.

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

5. Using JPA Specifications

5.使用JPA规范

Spring Data introduced the org.springframework.data.jpa.domain.Specification interface to encapsulate a single predicate:

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

interface Specification<T> {
    Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}

We can provide methods to create Specification instances:

我们可以提供方法来创建Specification实例。

static Specification<Book> hasAuthor(String author) {
    return (book, cq, cb) -> cb.equal(book.get("author"), author);
}

static Specification<Book> titleContains(String title) {
    return (book, cq, cb) -> cb.like(book.get("title"), "%" + title + "%");
}

To use them, we need our repository to extend org.springframework.data.jpa.repository.JpaSpecificationExecutor<T>:

为了使用它们,我们需要我们的存储库扩展org.springframework.data.jpa.repository.JpaSpecificationExecutor<T>

interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificationExecutor<Book> {}

This interface declares handy methods to work with specifications. For example, now we can find all Book instances with the specified author with this one-liner:

这个接口声明了一些方便的方法来处理规范。例如,现在我们可以通过这个单行代码找到所有具有指定作者的实例。

bookRepository.findAll(hasAuthor(author));

Unfortunately, we don’t get any methods that we can pass multiple Specification arguments to. Rather, we get utility methods in the org.springframework.data.jpa.domain.Specification interface.

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

For example, we can combine two Specification instances with a logical and:

例如,我们可以用逻辑的and组合两个Specification实例。

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

In the example above, where() is a static method of the Specification class.

在上面的例子中,where()Specification类的一个静态方法。

This way we can make our queries modular. Besides, we didn’t have to write the Criteria API boilerplate because Spring provided it for us.

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

Note that it doesn’t mean we won’t have to write criteria boilerplate anymore; this approach is only capable of handling the workflow we saw, namely selecting entities that satisfy the provided condition(s).

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

A query can have many structures it doesn’t support, including grouping, returning a different class than we’re selecting from, or subqueries.

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

6. Conclusion

6.结语

In this article, we discusssed three ways to use criteria queries in our Spring application:

在这篇文章中,我们讨论了在Spring应用程序中使用标准查询的三种方法。

  • creating a DAO class is the most straightforward and flexible way.
  • extending a @Repository interface to seamless integration with automatic queries
  • using predicates in Specification instances to make the simple cases cleaner and less verbose

As usual, the examples are available over on GitHub.

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