2. 教程
我们为 Querydsl 的主要后端提供集成指南,而不是一般的入门指南。
2.1. Querying JPA(查询JPA)
Querydsl 定义了一种通用的静态类型语法,用于在持久域模型数据之上进行查询。 JDO 和 JPA 是 Querydsl 的主要集成技术。 本指南介绍了如何将 Querydsl 与 JPA 结合使用。
Querydsl for JPA 是 JPQL 和 Criteria 查询的替代方案。 它以完全类型安全的方式将 Criteria 查询的动态特性与 JPQL 的表达能力以及所有这些结合起来。
2.1.1. Maven集成
将以下依赖项添加到您的 Maven 项目中:
<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>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
现在,配置 Maven APT 插件:
<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>
...
</plugins>
</build>
</project>
JPAAnnotationProcessor
查找使用 javax.persistence.Entity
注解的实体类并为它们生成查询类。
如果您在实体类中使用 Hibernate 注解,则应改用 APT 处理器 com.querydsl.apt.hibernate.HibernateAnnotationProcessor
。
运行maven clean install
,您将得到生成的查询类到 target/generated-sources/java
目录下。
如果您使用 Eclipse,请运行 mvn eclipse:eclipse
来更新您的 Eclipse 项目以包含 target/generated-sources/java
作为源文件夹。
现在您可以构建 JPA 查询实例和实体查询类的实例。
2.1.2. Ant集成
将 full-deps 包中的 jar 文件放在类路径上,并使用以下任务生成 Querydsl 代码:
<!-- APT based code generation -->
<javac srcdir="${src}" classpathref="cp">
<compilerarg value="-proc:only"/>
<compilerarg value="-processor"/>
<compilerarg value="com.querydsl.apt.jpa.JPAAnnotationProcessor"/>
<compilerarg value="-s"/>
<compilerarg value="${generated}"/>
</javac>
<!-- compilation -->
<javac classpathref="cp" destdir="${build}">
<src path="${src}"/>
<src path="${generated}"/>
</javac>
将 src 替换为您的主源文件夹,将 generated 替换为用于生成源的文件夹,将 build 替换为您的目标文件夹。
2.1.3. 在 Roo 中使用 Querydsl JPA
如果您将 Querydsl JPA 与 Spring Roo 一起使用,您可以将com.querydsl.apt.jpa.JPAAnnotationProcessor
替换为com.querydsl.apt.roo.RooAnnotationProcessor
,将处理@RooJpaEntity
和@RooJpaActiveRecord
注解的类,而不是@Entity
注解的类。
基于 APT 的代码生成不适用于 AspectJ IDT。
2.1.4. 从hbm.xml文件生成模型
如果您将 Hibernate 与基于 XML 的配置一起使用,您可以使用 XML 元数据来创建您的 Querydsl 模型。
com.querydsl.jpa.codegen.HibernateDomainExporter
提供了以下功能:
HibernateDomainExporter exporter = new HibernateDomainExporter(
"Q", // name prefix
new File("target/gen3"), // target folder
configuration); // instance of org.hibernate.cfg.Configuration
exporter.export();
HibernateDomainExporter 需要在实体类型可见的类路径中执行,因为属性类型是通过反射解析的。
所有 JPA 注解都被忽略,但 Querydsl 注解如 @QueryInit
和 @QueryType
被考虑在内。
2.1.5. 使用查询类型
要使用Querydsl创建查询,需要实例化变量和Query实现。我们将从变量开始。
让我们假设你的项目有以下实体类型:
@Entity
public class Customer {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public void setFirstName(String fn) {
firstName = fn;
}
public void setLastName(String ln) {
lastName = ln;
}
}
Querydsl 将在与 Customer 相同的包中生成一个名称为 QCustomer 的查询类型。 QCustomer 可以用作 Querydsl 查询中的静态类型变量,作为 Customer 类型的代表。
QCustomer 有一个可以作为静态字段访问的默认实例变量:
QCustomer customer = QCustomer.customer;
或者,您可以像这样定义自己的 Customer 变量:
QCustomer customer = new QCustomer("myCustomer");
2.1.6. 查询
Querydsl JPA 模块支持 JPA 和 Hibernate API。
要使用 JPA API,您可以使用 JPAQuery
实例进行查询,如下所示:
// where entityManager is a JPA EntityManager
JPAQuery<?> query = new JPAQuery<Void>(entityManager);
如果您使用的是 Hibernate API,则可以像这样实例化一个 HibernateQuery
:
// where session is a Hibernate session
HibernateQuery<?> query = new HibernateQuery<Void>(session);
JPAQuery
和 HibernateQuery
都实现了 JPQLQuery
接口。
对于本章的示例,查询是通过JPAQueryFactory
实例创建的。
💡提示:
JPAQueryFactory
应该是获取JPAQuery
实例的首选选项。
可以使用 Hibernate API HibernateQueryFactory
要检索名字为 Bob 的客户,您将构造如下查询:
QCustomer customer = QCustomer.customer;
Customer bob = queryFactory.selectFrom(customer)
.where(customer.firstName.eq("Bob"))
.fetchOne();
selectFrom 调用定义了查询源和投影, where 部分定义了过滤器, fetchOne 告诉 Querydsl 返回单个元素。 很简单,对吧?
要使用多个源创建查询,您可以使用如下查询:
QCustomer customer = QCustomer.customer;
QCompany company = QCompany.company;
query.from(customer, company);
要使用多重过滤就像这样
queryFactory.selectFrom(customer)
.where(customer.firstName.eq("Bob"), customer.lastName.eq("Wilson"));
或者像这样
queryFactory.selectFrom(customer)
.where(customer.firstName.eq("Bob").and(customer.lastName.eq("Wilson")));
在原生 JPQL 形式中,查询将这样编写:
select customer from Customer as customer
where customer.firstName = "Bob" and customer.lastName = "Wilson"
如果要通过“或”组合过滤器,请使用以下模式
queryFactory.selectFrom(customer)
.where(customer.firstName.eq("Bob").or(customer.lastName.eq("Wilson")));
2.1.7. 使用连接
Querydsl 支持 JPQL 中的以下连接变体:内连接、连接、左连接和右连接。 联接使用是类型安全的,并遵循以下模式:
QCat cat = QCat.cat;
QCat mate = new QCat("mate");
QCat kitten = new QCat("kitten");
queryFactory.selectFrom(cat)
.innerJoin(cat.mate, mate)
.leftJoin(cat.kittens, kitten)
.fetch();
查询的native JPQL 版本将是
select cat from Cat as cat
inner join cat.mate as mate
left outer join cat.kittens as kitten
另一个例子
queryFactory.selectFrom(cat)
.leftJoin(cat.kittens, kitten)
.on(kitten.bodyWeight.gt(10.0))
.fetch();
相应的JPQL 版本将是
select cat from Cat as cat
left join cat.kittens as kitten
on kitten.bodyWeight > 10.0
2.1.8. 一般用法
像这样使用 JPQLQuery 接口的级联方法
-
select: 设置查询的投影。 (如果通过查询工厂创建则不需要)
-
from: 在此处添加查询源。
-
innerJoin, join, leftJoin, rightJoin, on: 使用这些构造添加连接元素。 对于连接方法,第一个参数是连接源,第二个参数是目标(别名)。
-
where: 添加查询过滤器,以逗号分隔的可变参数形式或通过 and 运算符级联。
-
groupBy: 以可变参数形式添加 group by 参数。
-
having: 添加具有“group by”分组的过滤器作为谓词表达式的 varags 数组。
-
orderBy: 将结果的排序添加为顺序表达式的可变参数数组。 在数字、字符串和其他可比较的表达式上使用 asc() 和 desc() 来访问 OrderSpecifier 实例。
-
limit, offset, restrict: 设置结果的分页。 最大结果的限制,跳过行的偏移量和在一次调用中定义两者的限制。
2.1.9. 排序
声明排序的语法是
QCustomer customer = QCustomer.customer;
queryFactory.selectFrom(customer)
.orderBy(customer.lastName.asc(), customer.firstName.desc())
.fetch();
相当于下面的原生 JPQL
select customer from Customer as customer
order by customer.lastName asc, customer.firstName desc
2.1.10. 分组
可以按以下形式进行分组
queryFactory.select(customer.lastName).from(customer)
.groupBy(customer.lastName)
.fetch();
相当于下面的原生 JPQL
select customer.lastName
from Customer as customer
group by customer.lastName
2.1.11. 删除子句
Querydsl JPA 中的删除子句遵循简单的 delete-where-execute 形式。 这里有些例子:
QCustomer customer = QCustomer.customer;
// delete all customers
queryFactory.delete(customer).execute();
// delete all customers with a level less than 3
queryFactory.delete(customer).where(customer.level.lt(3)).execute();
where 调用是可选的,execute 调用执行删除并返回已删除实体的数量。
💡提示: JPA中的DML子句没有考虑JPA级别的级联规则,也不提供细粒度的二级缓存交互。
2.1.12. 更新子句
Querydsl JPA 中的更新子句遵循简单的 update-set/where-execute 形式。 这里有些例子:
QCustomer customer = QCustomer.customer;
// rename customers named Bob to Bobby
queryFactory.update(customer).where(customer.name.eq("Bob"))
.set(customer.name, "Bobby")
.execute();
set调用以sql -Update样式定义属性更新,而execute调用执行Update并返回更新的实体数量。
💡提示: JPA中的DML子句没有考虑JPA级别的级联规则,也不提供细粒度的二级缓存交互。
2.1.13. 子查询
要创建子查询,您可以使用JPAExpressions
的静态工厂方法并通过 from
、where
等定义查询参数。
QDepartment department = QDepartment.department;
QDepartment d = new QDepartment("d");
queryFactory.selectFrom(department)
.where(department.size.eq(
JPAExpressions.select(d.size.max()).from(d)))
.fetch();
另一个例子
QEmployee employee = QEmployee.employee;
QEmployee e = new QEmployee("e");
queryFactory.selectFrom(employee)
.where(employee.weeklyhours.gt(
JPAExpressions.select(e.weeklyhours.avg())
.from(employee.department.employees, e)
.where(e.manager.eq(employee.manager))))
.fetch();
2.1.14. 使用原始JPA的Query
如果你需要在执行查询之前调整原始JPA Query(javax.persistence.Query),你可以像这样暴露它:
javax.persistence.Query jpaQuery = queryFactory.selectFrom(QEmployee.employee).createQuery();
// ...
@SuppressWarnings("unchecked")
List<Employee> results = (List<Employee>) jpaQuery.getResultList();
2.1.15. 在JPA查询中使用Native SQL
Querydsl通过JPASQLQuery
类在JPA中支持Native SQL。
要使用它,您必须为您的 SQL 模式生成 Querydsl 查询类型。 例如,这可以通过以下 Maven 配置来完成:
<project>
<build>
<plugins>
...
<plugin>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-maven-plugin</artifactId>
<version>${querydsl.version}</version>
<executions>
<execution>
<goals>
<goal>export</goal>
</goals>
</execution>
</executions>
<configuration>
<jdbcDriver>org.apache.derby.jdbc.EmbeddedDriver</jdbcDriver>
<jdbcUrl>jdbc:derby:target/demoDB;create=true</jdbcUrl>
<packageName>com.mycompany.mydomain</packageName>
<targetFolder>${project.basedir}/target/generated-sources/java</targetFolder>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>${derby.version}</version>
</dependency>
</dependencies>
</plugin>
...
</plugins>
</build>
</project>
当查询类型成功生成到您选择的位置时,您可以在查询中使用它们。
单列查询:
// serialization templates
SQLTemplates templates = new DerbyTemplates();
// query types (S* for SQL, Q* for domain types)
SAnimal cat = new SAnimal("cat");
SAnimal mate = new SAnimal("mate");
QCat catEntity = QCat.cat;
JPASQLQuery<?> query = new JPASQLQuery<Void>(entityManager, templates);
List<String> names = query.select(cat.name).from(cat).fetch();
如果您在查询中混合使用实体(例如 QCat)和表(例如 SAnimal)引用,您需要确保它们使用相同的变量名称。 SAnimal.animal 具有变量名称“animal”,因此使用了一个新实例 (new SAnimal(“cat”))。
另一种模式可能是
QCat catEntity = QCat.cat;
SAnimal cat = new SAnimal(catEntity.getMetadata().getName());
查询多列:
query = new JPASQLQuery<Void>(entityManager, templates);
List<Tuple> rows = query.select(cat.id, cat.name).from(cat).fetch();
查询所有列:
List<Tuple> rows = query.select(cat.all()).from(cat).fetch();
在 SQL 中查询,但投影为实体:
query = new JPASQLQuery<Void>(entityManager, templates);
List<Cat> cats = query.select(catEntity).from(cat).orderBy(cat.name.asc()).fetch();
使用连接查询:
query = new JPASQLQuery<Void>(entityManager, templates);
cats = query.select(catEntity).from(cat)
.innerJoin(mate).on(cat.mateId.eq(mate.id))
.where(cat.dtype.eq("Cat"), mate.dtype.eq("Cat"))
.fetch();
查询并投影到 DTO:
query = new JPASQLQuery<Void>(entityManager, templates);
List<CatDTO> catDTOs = query.select(Projections.constructor(CatDTO.class, cat.id, cat.name))
.from(cat)
.orderBy(cat.name.asc())
.fetch();
如果您使用的是 Hibernate API 而不是 JPA API,那么请使用HibernateSQLQuery
。