Intro to Querydsl – Querydsl介绍

最后修改: 2016年 7月 6日

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

1. Introduction

1.介绍

This is an introductory article to get you up and running with the powerful Querydsl API for data persistence.

这是一篇介绍性文章,旨在让您使用强大的Querydsl API来实现数据持久性。

The goal here is to give you the practical tools to add Querydsl into your project, understand the structure and purpose of the generated classes, and get a basic understanding of how to write type-safe database queries for most common scenarios.

这里的目标是给你提供实用的工具,将Querydsl添加到你的项目中,了解生成的类的结构和目的,并对如何为大多数常见的场景编写类型安全的数据库查询有一个基本了解。

2. The Purpose of Querydsl

2.Querydsl的目的

Object-relational mapping frameworks are at the core of Enterprise Java. These compensate the mismatch between object-oriented approach and relational database model. They also allow developers to write cleaner and more concise persistence code and domain logic.

对象-关系映射框架是企业Java的核心。这些框架弥补了面向对象方法和关系数据库模型之间的不匹配。它们还允许开发者编写更干净、更简洁的持久化代码和领域逻辑。

However, one of the most difficult design choices for an ORM framework is the API for building correct and type-safe queries.

然而,ORM框架最困难的设计选择之一是用于建立正确和类型安全的查询的API。

One of the most widely used Java ORM frameworks, Hibernate (and also closely related JPA standard), proposes a string-based query language HQL (JPQL) very similar to SQL. The obvious drawbacks of this approach are the lack of type safety and absence of static query checking. Also, in more complex cases (for instance, when the query needs to be constructed at runtime depending on some conditions), building an HQL query typically involves concatenation of strings which is usually very unsafe and error-prone.

最广泛使用的Java ORM框架之一,Hibernate(以及密切相关的JPA标准),提出了一种基于字符串的查询语言HQL(JPQL),与SQL非常相似。这种方法的明显缺点是缺乏类型安全和没有静态查询检查。此外,在更复杂的情况下(例如,当查询需要根据某些条件在运行时构建),构建HQL查询通常涉及字符串的连接,这通常是非常不安全和容易出错的。

The JPA 2.0 standard brought an improvement in the form of Criteria Query API — a new and type-safe method of building queries that took advantage of metamodel classes generated during annotation preprocessing. Unfortunately, being groundbreaking in its essence, Criteria Query API ended up very verbose and practically unreadable. Here’s an example from Jakarta EE tutorial for generating a query as simple as SELECT p FROM Pet p:

JPA 2.0标准以标准查询API的形式带来了改进–这是一种新的、类型安全的构建查询的方法,它利用了在注释预处理期间生成的元模型类。不幸的是,由于其本质上的突破性,标准查询API最终变得非常冗长,几乎无法阅读。下面是Jakarta EE教程中的一个例子,用于生成一个像SELECT p FROM Pet p这样简单的查询。

EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.select(pet);
TypedQuery<Pet> q = em.createQuery(cq);
List<Pet> allPets = q.getResultList();

No wonder that a more adequate Querydsl library soon emerged, based on the same idea of generated metadata classes, yet implemented with a fluent and readable API.

难怪很快就出现了一个更充分的Querydsl库,它基于生成元数据类的相同想法,但却以流畅和可读的API实现。

3. Querydsl Class Generation

3.Querydsl类的生成

Let’s start with generating and exploring the magical metaclasses that account for the fluent API of Querydsl.

让我们从生成和探索神奇的元类开始,这些元类说明了Querydsl的流畅的API。

3.1. Adding Querydsl to Maven Build

3.1.将Querydsl添加到Maven Build中

Including Querydsl in your project is as simple as adding several dependencies to your build file and configuring a plugin for processing JPA annotations. Let’s start with the dependencies. The version of Querydsl libraries should be extracted to a separate property inside the <project><properties> section, as follows (for the latest version of Querydsl libraries, check the Maven Central repository):

在你的项目中加入Querydsl,就像在你的构建文件中加入几个依赖项和配置一个处理JPA注释的插件一样简单。让我们从依赖性开始。Querydsl库的版本应该被提取到<project><properties>部分的单独属性中,如下所示(关于Querydsl库的最新版本,请查看Maven Central资源库)。

<properties>
    <querydsl.version>4.1.3</querydsl.version>
</properties>

Next, add the following dependencies to the <project><dependencies> section of your pom.xml file:

接下来,在你的<project><dependencies>文件的pom.xml部分添加以下依赖项。

<dependencies>

    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>${querydsl.version}</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
        <version>${querydsl.version}</version>
    </dependency>

</dependencies>

The querydsl-apt dependency is an annotation processing tool (APT) — implementation of corresponding Java API that allows processing of annotations in source files before they move on to the compilation stage. This tool generates the so called Q-types — classes that directly relate to the entity classes of your application, but are prefixed with letter Q. For instance, if you have a User class marked with the @Entity annotation in your application, then the generated Q-type will reside in a QUser.java source file.

querydsl-apt依赖性是一个注释处理工具(APT)–实现了相应的Java API,允许在源文件进入编译阶段之前处理其注释。例如,如果你的应用程序中有一个User类,并标有@Entity注解,那么生成的Q类型将驻留在QUser.java源文件中。

The provided scope of the querydsl-apt dependency means that this jar should be made available only at build time, but not included into the application artifact.

provided scope of the querydsl-apt dependency 意味着这个 jar 应该只在构建时可用,但不包括在应用程序的工件中。

The querydsl-jpa library is the Querydsl itself, designed to be used together with a JPA application.

querydsl-jpa库是Querydsl本身,被设计为与JPA应用程序一起使用。

To configure annotation processing plugin that takes advantage of querydsl-apt, add the following plugin configuration to your pom – inside the <project><build><plugins> element:

要配置利用querydsl-apt的注释处理插件,请在你的pom中添加以下插件配置–在<project><build><plugins>元素内。

<plugin>
    <groupId>com.mysema.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
        <execution>
            <goals>
                <goal>process</goal>
            </goals>
            <configuration>
                <outputDirectory>target/generated-sources/java</outputDirectory>
                <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
        </execution>
    </executions>
</plugin>

This plugin makes sure that the Q-types are generated during the process goal of Maven build. The outputDirectory configuration property points to the directory where the Q-type source files will be generated. The value of this property will be useful later on, when you’ll go exploring the Q-files.

该插件可确保在Maven构建的过程目标中生成Q-type。outputDirectory配置属性指向生成Q型源文件的目录。这个属性的值在以后探索Q型文件的时候会很有用。

You should also add this directory to the source folders of the project, if your IDE does not do this automatically — consult the documentation for your favorite IDE on how to do that.

如果你的IDE没有自动这样做,你也应该把这个目录添加到项目的源文件夹中–关于如何做,请查阅你喜欢的IDE的文档。

For this article we will use a simple JPA model of a blog service, consisting of Users and their BlogPosts with a one-to-many relationship between them:

在这篇文章中,我们将使用一个简单的博客服务JPA模型,由Users和他们的BlogPosts组成,它们之间是一对多的关系。

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String login;

    private Boolean disabled;

    @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "user")
    private Set<BlogPost> blogPosts = new HashSet<>(0);

    // getters and setters

}

@Entity
public class BlogPost {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String body;

    @ManyToOne
    private User user;

    // getters and setters

}

To generate Q-types for your model, simply run:

要为你的模型生成Q-类型,只需运行。

mvn compile

3.2. Exploring Generated Classes

3.2.探索生成的类

Now go to the directory specified in the outputDirectory property of apt-maven-plugin (target/generated-sources/java in our example). You will see a package and class structure that directly mirrors your domain model, except all the classes start with letter Q (QUser and QBlogPost in our case).

现在进入apt-maven-plugin的outputDirectory属性中指定的目录(本例中为target/generated-sources/java)。你会看到一个包和类的结构,直接反映了你的领域模型,只是所有的类都以字母Q开头(在我们的例子中是QUserQBlogPost)。

Open the file QUser.java. This is your entry point to building all queries that have User as a root entity. First thing you’ll notice is the @Generated annotation which means that this file was automatically generated and should not be edited manually. Should you change any of your domain model classes, you will have to run mvn compile again to regenerate all of the corresponding Q-types.

打开文件QUser.java。这是你建立所有以User为根实体的查询的入口。你首先会注意到@Generated注解,这意味着这个文件是自动生成的,不应该被手动编辑。如果你改变了任何领域模型类,你将不得不再次运行mvn compile来重新生成所有相应的Q-types。

Aside from several QUser constructors present in this file, you should also take notice of a public static final instance of the QUser class:

除了这个文件中存在的几个QUser构造函数外,你还应该注意到QUser类的一个公共静态最终实例。

public static final QUser user = new QUser("user");

This is the instance that you can use in most of your Querydsl queries to this entity, except when you need to write some more complex queries, like joining several different instances of a table in a single query.

这是一个实例,你可以在你对这个实体的大多数查询中使用,除非你需要写一些更复杂的查询,比如在一个查询中连接一个表的几个不同实例。

The last thing that should be noted is that for every field of the entity class there is a corresponding *Path field in the Q-type, like NumberPath id, StringPath login and SetPath blogPosts in the QUser class (notice that the name of the field corresponding to Set is pluralized). These fields are used as parts of fluent query API that we will encounter later on.

最后需要注意的是,对于实体类的每一个字段,在Q类型中都有一个相应的*Path字段,比如NumberPath idStringPath loginSetPath blogPosts(注意,对应于Set的字段名是复数)。这些字段被用作流畅查询API的一部分,我们在后面会遇到。

4. Querying With Querydsl

4.用Querydsl进行查询

4.1. Simple Querying and Filtering

4.1.简单的查询和过滤

To build a query, first we’ll need an instance of a JPAQueryFactory, which is a preferred way of starting the building process. The only thing that JPAQueryFactory needs is an EntityManager, which should already be available in your JPA application via EntityManagerFactory.createEntityManager() call or @PersistenceContext injection.

要构建一个查询,首先我们需要一个JPAQueryFactory的实例,这是一个开始构建过程的首选方式。JPAQueryFactory唯一需要的是一个EntityManager,它应该已经通过EntityManagerFactory.createEntityManager()调用或@PersistenceContext注入在你的JPA应用程序中可用。

EntityManagerFactory emf = 
  Persistence.createEntityManagerFactory("com.baeldung.querydsl.intro");
EntityManager em = entityManagerFactory.createEntityManager();
JPAQueryFactory queryFactory = new JPAQueryFactory(em);

Now let’s create our first query:

现在让我们创建我们的第一个查询。

QUser user = QUser.user;

User c = queryFactory.selectFrom(user)
  .where(user.login.eq("David"))
  .fetchOne();

Notice we’ve defined a local variable QUser user and initialized it with QUser.user static instance. This is done purely for brevity, alternatively you may import the static QUser.user field.

注意我们定义了一个局部变量QUser user,并用QUser.user静态实例对其进行初始化。这样做纯粹是为了简洁,或者你可以导入静态QUser.user字段。

The selectFrom method of the JPAQueryFactory starts building a query. We pass it the QUser instance and continue building the conditional clause of the query with the .where() method. The user.login is a reference to a StringPath field of the QUser class that we’ve seen before. The StringPath object also has the .eq() method that allows to fluently continue building the query by specifying the field equality condition.

JPAQueryFactoryselectFrom方法开始构建一个查询。我们将QUser实例传递给它,并继续用.where()方法构建查询的条件条款。user.login是对QUser类的StringPath字段的引用,我们之前已经看到了。StringPath对象也有.eq()方法,允许通过指定字段的平等条件来流畅地继续构建查询。

Finally, to fetch the value from the database into persistence context, we end the building chain with the call to the fetchOne() method. This method returns null if the object can’t be found, but throws a NonUniqueResultException if there are multiple entities satisfying the .where() condition.

最后,为了从数据库中获取值到持久化上下文,我们通过调用fetchOne()方法来结束构建链。如果找不到对象,该方法返回null,但如果有多个实体满足.where()条件,则抛出NonUniqueResultException

4.2. Ordering and Grouping

4.2.排序和分组

Now let’s fetch all users in a list, sorted by their login in ascension order.

现在,让我们在一个列表中获取所有的用户,按照他们的登录名以升序排序。

List<User> c = queryFactory.selectFrom(user)
  .orderBy(user.login.asc())
  .fetch();

This syntax is possible because the *Path classes have the .asc() and .desc() methods. You can also specify several arguments for the .orderBy() method to sort by multiple fields.

这种语法是可能的,因为*Path类有.asc().desc()方法。你也可以为.orderBy()方法指定几个参数,以按多个字段排序。

Now let’s try something more difficult. Suppose we need to group all posts by title and count duplicating titles. This is done with the .groupBy() clause. We’ll also want to order the titles by resulting occurrence count.

现在让我们来试试更困难的事情。假设我们需要按标题对所有帖子进行分组,并计算重复的标题。这可以通过.groupBy()子句来完成。我们还想根据结果的出现次数对标题进行排序。

NumberPath<Long> count = Expressions.numberPath(Long.class, "c");

List<Tuple> userTitleCounts = queryFactory.select(
  blogPost.title, blogPost.id.count().as(count))
  .from(blogPost)
  .groupBy(blogPost.title)
  .orderBy(count.desc())
  .fetch();

We selected the blog post title and count of duplicates, grouping by title and then ordering by aggregated count. Notice we first created an alias for the count() field in the .select() clause, because we needed to reference it in the .orderBy() clause.

我们选择了博客文章的标题和重复数,按标题分组,然后按汇总的计数排序。注意我们首先在.select()子句中为count()字段创建了一个别名,因为我们需要在.orderBy()子句中引用它。

4.3. Complex Queries With Joins and Subqueries

4.3.使用连接和子查询的复杂查询

Let’s find all users that wrote a post titled “Hello World!” For such query we could use an inner join. Notice we’ve created an alias blogPost for the joined table to reference it in the .on() clause:

让我们找到所有写了一篇题为 “Hello World!”的帖子的用户。对于这样的查询,我们可以使用一个内部连接。注意我们已经为连接表创建了一个别名blogPost,以便在.on()子句中引用它。

QBlogPost blogPost = QBlogPost.blogPost;

List<User> users = queryFactory.selectFrom(user)
  .innerJoin(user.blogPosts, blogPost)
  .on(blogPost.title.eq("Hello World!"))
  .fetch();

Now let’s try to achieve the same with subquery:

现在让我们尝试用子查询来实现同样的目的。

List<User> users = queryFactory.selectFrom(user)
  .where(user.id.in(
    JPAExpressions.select(blogPost.user.id)
      .from(blogPost)
      .where(blogPost.title.eq("Hello World!"))))
  .fetch();

As we can see, subqueries are very similar to queries, and they are also quite readable, but they start with JPAExpressions factory methods. To connect subqueries with the main query, as always, we reference the aliases defined and used earlier.

正如我们所看到的,子查询与查询非常相似,而且它们也很好读,但它们以JPAExpressions工厂方法开始。为了将子查询与主查询连接起来,像往常一样,我们引用前面定义和使用的别名。

4.4. Modifying Data

4.4.修改数据

JPAQueryFactory allows not only constructing queries, but also modifying and deleting records. Let’s change the user’s login and disable the account:

JPAQueryFactory不仅可以构造查询,还可以修改和删除记录。让我们改变用户的登录方式并禁用该账户。

queryFactory.update(user)
  .where(user.login.eq("Ash"))
  .set(user.login, "Ash2")
  .set(user.disabled, true)
  .execute();

We can have any number of .set() clauses we want for different fields. The .where() clause is not necessary, so we can update all the records at once.

我们可以为不同的字段设置任意数量的.set()子句。.where()子句是不必要的,所以我们可以一次性更新所有记录。

To delete the records matching a certain condition, we can use a similar syntax:

要删除符合某个条件的记录,我们可以使用类似的语法。

queryFactory.delete(user)
  .where(user.login.eq("David"))
  .execute();

The .where() clause is also not necessary, but be careful, because omitting the .where() clause results in deleting all of the entities of a certain type.

.where()子句也是不必要的,但要小心,因为省略.where()子句的结果是删除所有某一类型的实体。

You may wonder, why JPAQueryFactory doesn’t have the .insert() method. This is a limitation of JPA Query interface. The underlying javax.persistence.Query.executeUpdate() method is capable of executing update and delete but not insert statements. To insert data, you should simply persist the entities with EntityManager.

你可能想知道,为什么JPAQueryFactory没有.insert()方法。这是JPA查询接口的一个限制。底层的javax.persistence.Query.executeUpdate()方法能够执行更新和删除,但不能执行插入语句。要插入数据,你应该简单地用EntityManager来持久化实体。

If you still want to take advantage of a similar Querydsl syntax for inserting data, you should use SQLQueryFactory class that resides in the querydsl-sql library.

如果你仍然想利用类似的Querydsl语法来插入数据,你应该使用驻扎在querydsl-sql库中的SQLQueryFactory类。

5. Conclusion

5.结论

In this article we’ve discovered a powerful and type-safe API for persistent object manipulation that is provided by Querydsl.

在这篇文章中,我们发现了一个强大的、类型安全的持久化对象操作的API,它是由Querydsl提供的。

We’ve learned to add Querydsl to project and explored the generated Q-types. We’ve also covered some typical use cases and enjoyed their conciseness and readability.

我们已经学会了在项目中添加Querydsl,并探索了生成的Q-types。我们还介绍了一些典型的用例,并享受其简洁和可读性。

All the source code for the examples can be found in the github repository.

所有例子的源代码都可以在github资源库中找到。

Finally, there are of course many more features that Querydsl provides, including working with raw SQL, non-persistent collections, NoSQL databases and full-text search – and we’ll explore some of these in future articles.

最后,当然还有许多Querydsl提供的功能,包括处理原始SQL、非持久性集合、NoSQL数据库和全文搜索–我们将在未来的文章中探讨其中一些功能。