Querydsl
1. 介绍
1.1. 背景
Querydsl 能够诞生,是因为需要在类型安全的方式下进行HQL查询。构造的HQL查询需要拼接字符串,并且会导致代码难以阅读。通过纯字符串对领域类型和属性的不安全引用是基于字符串构建HQL的另一个问题。
随着类型安全的领域模型的不断的发展,给软件开发带了巨大的好处。领域最大的改变直接反应在查询和自动查询的构建,它使查询构建更快且安全。
Querydsl最先支持的是Hibernate的HQL语言,现如今,Querydsl已经支持JPA,JDO,JDBC,Lucene,Hibernate Search,MangoDB,Collections 和RDF(Relational Data Format) Bean作为后端。
1.2. 原则
类型安全是Querydsl的核心原则。查询是基于与领域类型的属性映射生成的查询类型构建的。同时,函数/方法的调用也是使用完全的类型安全的方式构建的。
保持一致是另一个重要的原则。查询路径(Path)和操作在所有实现中都是相同的,它们都具有一个公用的基本接口。
要想获得更多Querydsl查询和表达式的说明,请查看javadoc中的 com.querydsl.core.Query,com.querydsl.core.Fetchable 和 com.querydsl.core.types.Expression 类的文档。
教程
与一般的入门指南不同,我们提供了以Querydsl为主后端的综合教程。
- Querying JPA
- Querying SQL
-
2.1. Querying JPA
Querydsl 定义了一个通用静态类型的语法用于查询持久化的领域模型数据。JDO 和 JPA 是 Querydsl 主要的集成技术。这篇手册介绍了如何让Querydsl与JPA整合使用。
Querydsl JPA 是JPQL和标准条件查询(Criteria queries)的新的使用方式。它结合了条件查询的动态性和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来代替JPAAnnotationProcessor。运行
clean install命令后,在target/generated-sources/java目录下将生成相应的查询类型。
如果你使用Eclipse,运行mvn eclipse:eclipse更新你的Eclipse项目,将target/generated-sources/java作为源目录。
现在,你就可以构建 JPA 查询实例和查询领域模型的实例了。2.1.2. Ant 集成
将 Querydsl 的所有依赖包(jar)放到你的 classpath 目录下,使用并执行下面的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替换为你需要生成的源代码的目标目录。2.1.3. 在 Roo 中使用 Querydsl JPA
如果你正在使用 Querydsl JPA 和 Spring Roo,你可以使用
com.querydsl.apt.roo.RooAnnotationProcessor替换com.querydsl.apt.jpa.JPAAnnotationProcessor,它将解析并处理@RooJpaEntity和@RooJpaActiveRecord注解,而不是@Entity。基于APT的代码生成器 不支持 与 AspectJ IDTs 整合。
2.1.4. 从 hbm.xml 文件中生成模型
如果你使用的Hibernate是基于XML配置的,那么你可以使用XML元数据来生成Querydsl模型。
com.querydsl.jpa.codegen.HibernateDomainExporter提供了这样的功能:HibernateDomainExporter exporter = new HibernateDomainExporter( "Q", // 名称前缀 new File("target/gen3"), // 生成的目标文件夹 configuration); // org.hibernate.cfg.Configuration 实例 exporter.export();HibernateDomainExporter需要在领域类型可见的classpath中执行,因为属性类型是通过反射机制获得的。所有的 JPA 注解都会被忽略,但是 Querydsl 的注解不会被忽略,比如
@QueryInit和@QueryType。2.1.5. 使用查询类型
To create queries with Querydsl you need to instantiate variables and Query implementations. We will start with the variables.
Let's assume that your project has the following domain type:
@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;或者你可以像下面列子一样定义查询类实例变量:
QCustomer customer = new QCustomer("myCustomer");2.1.6. 查询
Querydsl JPA 模块同时支持 JPA 和 Hibernate API。
当使用 JPA 时,需要用到
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" 的一个 customer 的信息的查询:
// queryFactory => JPAQueryFactory QCustomer customer = QCustomer.customer; Customer bob = queryFactory.selectFrom(customer) .where(customer.firstName.eq("Bob")) .fetchOne();调用
selectFrom方法是定义查询的来源与映射,where则定义查询的过滤器,fetchOne则要告诉 Querydsl 返回单个元素(SQLQuery<?>中的泛型指定元素类型)。很简单,不是吗!创建多资源(表)查询:
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"如果你想使用
"or"来组合条件过滤器,可以使用下面的方式:queryFactory.selectFrom(customer) .where(customer.firstName.eq("Bob").or(customer.lastName.eq("Wilson")));2.1.7. 联合查询
Querydsl 在JPQL中支持的联合查询:
inner join, join, left join, right join。联合查询是类型安全的,并且遵循下面的模式: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();下面是原生的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.02.1.8. 一般用法
JPQLQuery接口可以进行级联调用的方法说明:select: 设置查询的映射(如果由查询工厂创建查询,则不需要调用)from: 添加查询的源(表)innerJoin, join, leftJoin, rightJoin, on: 使用这些方法添加要join的元素。连接方法的第一个参数是要连接的源,第二个参数是源目标(别名)。where: 添加查询过滤器,以逗号分隔的可变参数传入,或是使用and或or操作连接。groupBy: 以可变参数形式添加查询的分组。having: 添加具有"GROUP BY"分组作为断言(Predicate)表达式的可变参数数组的HAVING过滤器orderBy: 添加结果的排序方式的排序表达式。使用asc()和desc()方法获取基于数字、字符串或其他可进行比较的表达式的OrderSpecifier实例limit, offset, restrict: 设置分页查询的结果。limit为结果的最大行数,offset是偏移的行数,restrict则是两者的限定约束2.1.9. 结果排序(Ordering)
声明结果排序:
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 desc2.1.10. 分组(Grouping)
下面的是对结果分组:
queryFactory.select(customer.lastName).from(customer) .groupBy(customer.lastName) .fetch();等效于下面的原生JPQL语句:
select customer.lastName from Customer as customer group by customer.lastName2.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-style方式定义要更新的属性,execute调用指行更新操作并返回被更新的实体的数量。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. 使用原始查询
如果你在查询执行前需要调整原有的查询,则可以像下面这样暴露她:
Query jpaQuery = queryFactory.selectFrom(employee).createQuery(); // ... List results = jpaQuery.getResultList();2.1.15. JPA 查询中使用本地SQL查询
Querydsl 支持通过
JPASQLQuery类在JPA中执行本地SQL查询。要使用它,你必须为SQL schema 生成 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(); // 查询类 (S* -> SQL, Q* -> 领域类) 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(Data Transform Object):
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即可。 -
2.3. Querying SQL
本章节主要讲述使用
Querydsl-SQL模块生成查询类型和进行查询的功能。2.3.1. Maven 整合
在你的
Maven项目中添加下列依赖:<dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql</artifactId> <version>${querydsl.version}</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql-codegen</artifactId> <version>${querydsl.version}</version> <scope>provided</scope> </dependency> -
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency>如果已经使用Maven插件生成了代码,则可以不需要
querydsl-sql-codegen这个依赖包。2.3.2. 通过Maven生成代码
这个功能主要通过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.myproject.domain</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>Use the goal test-export to treat the target folder as a test source folder for use with test code.
表 2.1. 参数说明
名称 说明 jdbcDriverJDBC 驱动支持的类名 jdbcUrlJDBC url jdbcUserJDBC user jdbcPasswordJDBC password namePrefix生成查询类类名的前缀,默认: "Q"nameSuffix生成查询类类名的后缀,默认: ""beanPrefix生成的 Bean类类名的前缀beanSuffix生成的 Bean类类名的前缀packageName生成的源文件的包名称 beanPackageName生成的 Bean文件的包名称,默认:packageNamebeanInterfaces生成的 Bean类实现的一组接口的类全名,默认:""beanAddToString设置 true时,生成的类中将自动添加toString()方法的实现,默认:falsebeanAddFullConstructor设置 true时,除了原始的默认构造方法外,还额外生成一个全参数完整的构造方法,默认:falsebeanPrintSupertype设置 true时,将同时打印出超类,默认:falseschemaPattern使用 LIKE模式中的schema名称模式;它必须匹配数据库存储中的schema名称tableNamePattern使用 LIKE模式中表名称模式;它必须匹配数据库中的表名称,多个模式使用逗号,分隔,默认:nulltargetFolder生成源文件的目标目录 beansTargetFolder生成的 Bean源文件的目标目录,默认与targetFolder设置的目录相同namingStrategyClass命名策略的实现类类名,默认: DefaultNamingStrategybeanSerializerClassBean序列化的实现类类名,默认:BeanSerializerserializerClass序列化实现类类名,默认: MetaDataSerializerexportBeans设置为 true时,同时导出Bean类,请参与第2.14.13部份,默认:falseinnerClassesForKeys设置为 true时,会为主键生成一个内部类,默认:falsevalidationAnnotations设置为 true时,序列化时将加入validation验证标签,默认:falsecolumnAnnotations导出列标签,默认: falsecreateScalaSources是否导出 Scala源文件,而不是Java源文件,默认:falseschemaToPackage追加 schema名称至包名称,默认:falselowerCase是否转换为小写名称,默认: falseexportTables导出数据表,默认: trueexportViews导出视图,默认: trueexportPrimaryKeys导出主键,默认: truetableTypesToExport导出使用逗号分隔的表类型的列表(可能的值取决于JDBC驱动)。允许导出任意的类型,例如: TABLE,MATERIALIZED VIEW。如果此值被设置,那么exportTables和exportViews的设置将会无效exportForeignKeys是否导出外键设置,默认: truecustomTypes用户自定义类型,默认: nonetypeMappingstable.column和JavaType的映射设置,默认:nonenumericMappingssize/digits和JavaType的映射设置,默认:noneimports生成的查询类中要引入的一组类: com.bar(引入包,不要加.*标识),com.bar.Foo(引入类)自定义类型转换的实现可以被额外添加:
<customTypes> <customType>com.querydsl.sql.types.InputStreamType</customType> </customTypes>可以注册指定的
table.column与JavaType的类型映射:<typeMappings> <typeMapping> <table>IMAGE</table> <column>CONTENTS</column> <type>java.io.InputStream</type> </typeMapping> </typeMappings>默认的数值映射:
表 2.2. 数值映射
总位数( Total digits)小数位数( Decimal digits)类型 > 18 0 BigInteger> 9 0 Long> 4 0 Integer> 2 0 Short> 0 0 Byte> 0 > 0 BigDecimal它们还可以用以下列方式定制:
<numericMappings> <numericMapping> <total>1</total> <decimal>0</decimal> <javaType>java.lang.Byte</javaType> </numericMapping> </numericMappings>Schema、表名和列名能够使用插件来重新命名,下面给出几个例子:schema的重命名<renameMappings> <renameMapping> <fromSchema>PROD</fromSchema> <toSchema>TEST</toSchema> </renameMapping> </renameMappings>表的重命名:
<renameMappings> <renameMapping> <fromSchema>PROD</fromSchema> <fromTable>CUSTOMER</fromTable> <toTable>CSTMR</toTable> </renameMapping> </renameMappings>列的重命名:
<renameMappings> <renameMapping> <fromSchema>PROD</fromSchema> <fromTable>CUSTOMER</fromTable> <fromColumn>ID</fromColumn> <toColumn>IDX</toTable> </renameMapping> </renameMappings>注:当表和列重命名时,
fromSchema可以被省略, 相比于APT(包管理工具)生成的代码,某此功能将不可用,例如,QueryDelegate注解功能2.3.3. 使用Ant生成代码
使用 Querydsl-SQL 模块的
com.querydsl.sql.codegen.ant.AntMetaDataExporter作为 ANT任务支持,提供了相同的功能,Ant 的 task 配置与 Maven 配置基本相同,作了复合型类型外。复合类型要使用没有包装的元素(ANT: without wrapper element)
<project name="testproject" default="codegen" basedir="."> <taskdef name="codegen" classname="com.querydsl.sql.codegen.ant.AntMetaDataExporter";/> <target name="codegen"> <codegen jdbcDriver="org.h2.Driver" jdbcUser="sa" jdbcUrl="jdbc:h2:/dbs/db1" packageName="test" targetFolder="target/generated-sources/java"> <renameMapping fromSchema="PUBLIC" toSchema="PUB"/> </codegen> </target> </project>2.3.4. 编码方式生成代码
下面是JAVA编码方式生成相关的查询类:
java.sql.Connection conn = ...; MetaDataExporter exporter = new MetaDataExporter(); exporter.setPackageName("com.myproject.mydomain"); exporter.setTargetFolder(new File("target/generated-sources/java")); exporter.export(conn.getMetaData());上面的代码说明,生成的查询类将会被生成至
target/generated-sources/java作为源目录的com.myproject.mydomainJAVA包目录中。 生成的查询类中,下划线连接的表名使用驼峰式命名作为查询类类名(USER_PROFILE=>QUserProfile),下划线连接的列名使用驼峰式命名作为查询类型路径(Path)属性的名称(user_id=>userId)。 另外,还生成了PrimaryKey和ForeignKey(如果有) 作为查询类属性,可用于进行join查询。2.3.5. 配置
使用
Querydsl-SQL的方言实现作为参数,构造一个com.querydsl.sql.Configuration作为配置信息。如果H2作为数据库,则可以这样做:SQLTemplates templates = new H2Templates(); Configuration configuration = new Configuration(templates);Querydsl 使用SQL方言来定制实现不同的关系数据库的SQL的序列化,目前可用的SQL方言实现有:
- CUBRIDTemplates (tested with CUBRID 8.4)
- DB2Templates (tested with DB2 10.1.2)
- DerbyTemplates (tested with Derby 10.8.2.2)
- FirebirdTemplates (tested with Firebird 2.5)
- HSQLDBTemplates (tested with HSQLDB 2.2.4)
- H2Templates (tested with H2 1.3.164)
- MySQLTemplates (tested with MySQL 5.5)
- OracleTemplates (test with Oracle 10 and 11)
- PostgreSQLTemplates (tested with PostgreSQL 9.1)
- SQLiteTemplates (tested with xerial JDBC 3.7.2)
- SQLServerTemplates (tested with SQL Server)
- SQLServer2005Templates (for SQL Server 2005)
- SQLServer2008Templates (for SQL Server 2008)
- SQLServer2012Templates (for SQL Server 2012 and later)
- TeradataTemplates (tested with Teradata 14)
为了构建
SQLTemplates实例,可以使用如下面中的builder模式:H2Templates.builder() .printSchema() // to include the schema in the output .quote() // to quote names .newLineToSingleSpace() // to replace new lines with single space in the output .escape(ch) // to set the escape char .build(); // to get the customized SQLTemplates instance通过
Configuration.setUseLiterals(true)方法来设置直接使用常量来序列化SQL语句,而不是使用参数式绑定的预编译语句,并且覆盖schema、table和自定义的类型,具体的说明请参见Configuration类的javadoc文档。2.3.6. 查询
下面的例子中,我们将使用工厂类
SQLQueryFactory来创建SQLQuery对象,它比使用SQLQuery的构造方法来创建实例更简便:SQLQueryFactory queryFactory = new SQLQueryFactory(configuration, dataSource);使用 Querydsl SQL 进行查询就是这么简单:
QCustomer customer = new QCustomer("c"); List<String> lastNames = queryFactory.select(customer.lastName).from(customer) .where(customer.firstName.eq("Bob")) .fetch();假设关系数据库中表名为
customer,列为first_name和last_name,则上述代码将转换为下列的SQL语句:SELECT c.last_name FROM customer c WHERE c.first_name = 'Bob'2.3.7. 一般用法
使用
SQLQuery类的级联方法调用select: 设置查询的映射(如果是通过SQLQueryFactory创建,则不是必须的)from: 添加查询的源(Q开头的查询映射对象)innerJoin, join, leftJoin, rightJoin, fullJoin, on: 添加要join的查询元素,join方法的第一个参数是join的查询元素,第二个参数是join的查询元素的目标(别名)where: 添加查询过滤器,以and操作符连接的,或是以逗号分隔的可变参数groupBy: 以可变参数的方式添加GROUP BY参数having: 添加具有"GROUP BY"分组作为断言(Predicate)表达式的可变参数数组的HAVING过滤器orderBy: 添加结果的排序方式的排序表达式。使用asc()和desc()方法获取基于数字、字符串或其他可进行比较的表达式的OrderSpecifier实例limit, offset, restrict: 设置分页查询的结果。limit为结果的最大行数,offset是偏移的行数,restrict则是两者的限定约束2.3.8. 联合查询
联合查询使用下面给出的语法来构建:
QCustomer customer = QCustomer.customer; QCompany company = QCompany.company; List<Customer> cList = queryFactory.select( customer.firstName, customer.lastName, company.name) .from(customer) .innerJoin(customer.company, company) .fetch();LEFT JOIN:queryFactory.select(customer.firstName, customer.lastName, company.name) .from(customer) .leftJoin(customer.company, company) .fetch();此外,还可以使用
on(..)加入join条件:queryFactory.select(customer.firstName, customer.lastName, company.name) .from(customer) .leftJoin(company).on(customer.company.eq(company.id)) .fetch();2.3.9. 结果排序
加入排序的语法
queryFactory.select(customer.firstName, customer.lastName) .from(customer) .orderBy(customer.lastName.asc(), customer.firstName.asc()) .fetch();相当于以下的SQL语句:
SELECT c.first_name, c.last_name FROM customer c ORDER BY c.last_name ASC, c.first_name ASC2.3.10. 分组查询
通过下面的方式进行分组查询
queryFactory.select(customer.lastName) .from(customer) .groupBy(customer.lastName) .fetch();上面的代码等效于下面的SQL语句:
SELECT c.last_name FROM customer c GROUP BY c.last_name2.3.11. 使用子查询
要创建一个子查询,可以使用
SQLExpressions的工厂方法,可以通过from、where等添加查询参数QCustomer customer = QCustomer.customer; QCustomer customer2 = new QCustomer("customer2"); queryFactory.select(customer.all()) .from(customer) .where(customer.status.eq( SQLExpressions.select(customer2.status.max()).from(customer2))) .fetch();2.3.12. 查询常量
要查询常量,你需要为他们创建相应的常量实例,就像下面这样:
queryFactory.select(Expressions.constant(1), Expressions.constant("abc"));com.querydsl.core.types.dsl.Expressions类还提供了映射,运算和创建模板等静态方法。2.3.13. 扩展查询的支持
可以通过继承
AbstractSQLQuery类并添加相应的标记(就像下列中的MySQLQuery的例子),用来支持指定数据库引擎的特有的语法。public class MySQLQuery<T> extends AbstractSQLQuery<T, MySQLQuery<T>> { public MySQLQuery(Connection conn) { this(conn, new MySQLTemplates(), new DefaultQueryMetadata()); } public MySQLQuery(Connection conn, SQLTemplates templates) { this(conn, templates, new DefaultQueryMetadata()); } protected MySQLQuery(Connection conn, SQLTemplates templates, QueryMetadata metadata) { super(conn, new Configuration(templates), metadata); } public MySQLQuery bigResult() { return addFlag(Position.AFTER_SELECT, "SQL_BIG_RESULT "); } public MySQLQuery bufferResult() { return addFlag(Position.AFTER_SELECT, "SQL_BUFFER_RESULT "); } // ... }此标记是可以在序列化后的特定点插入的自定义SQL片段。Querydsl 所支持的插入点是由
com.querydsl.core.QueryFlag.Position枚举类定义的。2.3.14. 窗口函数(Window functions)
在 Querydsl 中,
SQLExpressions的类方法用于支持数据库窗口函数。queryFactory.select(SQLExpressions.rowNumber() .over() .partitionBy(employee.name) .orderBy(employee.id)) .from(employee)2.3.15. 公用的表(table)表达式
在 Querydsl 中,通过两种不同的方式支持表(table)表达式
QEmployee employee = QEmployee.employee; queryFactory.with(employee, SQLExpressions.select(employee.all) .from(employee) .where(employee.name.startsWith("A"))) .from(...)使用列(columns)列表:
QEmployee employee = QEmployee.employee; queryFactory.with(employee, employee.id, employee.name) .as(SQLExpressions.select(employee.id, employee.name) .from(employee) .where(employee.name.startsWith("A"))) .from(...)如果公用表(table)表达式的列(columns)是一个已存在的表的子集或视图,建议使用已生成的
Path类型,例如本例中的QEmployee,但是,如果列不存在于任何的已有的表中,则使用PathBuilder来构建。
下列是对应这种情况下的一个例子:QEmployee employee = QEmployee.employee; QDepartment department = QDepartment.department; PathBuilder<Tuple> emp = new PathBuilder<Tuple>(Tuple.class, "emp"); queryFactory.with(emp, SQLExpressions.select( employee.id, employee.name, employee.departmentId, department.name.as("departmentName")) .from(employee) .innerJoin(department).on(employee.departmentId.eq(department.id)))) .from(...)2.3.16. 其他表达式
其他的表达式,同样可以调用
SQLExpression类中的相关的静态方法中获取或构建。2.3.17. 数据操作命令 (DML)
2.3.17.1. 插入(Insert)
带列(Column)方式:
QSurvey survey = QSurvey.survey; queryFactory.insert(survey) .columns(survey.id, survey.name) .values(3, "Hello").execute();不带列(Column)方式:
queryFactory.insert(survey).values(4, "Hello").execute();有子查询:
queryFactory.insert(survey) .columns(survey.id, survey.name) .select(SQLExpressions.select(survey2.id.add(1), survey2.name).from(survey2)) .execute();有子查询,不带列:
queryFactory.insert(survey) .select(SQLExpressions.select(survey2.id.add(10), survey2.name).from(survey2)) .execute();除了 columns/values 方式外,Querydsl 还提供了
set方法用于代替这一方式:QSurvey survey = QSurvey.survey; queryFactory.insert(survey) .set(survey.id, 3) .set(survey.name, "Hello").execute();这和第一个例子(带列方式插入)是相同的。内部实现中,Querydsl 自动映射列和指定的值。
要注意的是columns(...).select(...)将查询的结果映射至指定的列
如果要获取数据库自动生成的主键值,则不是获取受影响的行数,则使用executeWithKey方法。set(...)可用于映射单列,子查询返回空时,全部映射为
null值。使用一个bean实例,填充一个语句实例(SQLXxxxClause)
queryFactory.insert(survey) .populate(surveyBean).execute();上述代码将忽略
surveyBean中的值为null的属性,如果需要绑定null值属性,则需要:queryFactory.insert(survey) .populate(surveyBean, DefaultMapper.WITH_NULL_BINDINGS).execute();2.3.17.2. 更新(Update)
有
where:QSurvey survey = QSurvey.survey; queryFactory.update(survey) .where(survey.name.eq("XXX")) .set(survey.name, "S") .execute();没有
where:queryFactory.update(survey) .set(survey.name, "S") .execute();使用bean填充:
queryFactory.update(survey) .populate(surveyBean) .execute();2.3.17.3. 删除(Delete)
有
where:QSurvey survey = QSurvey.survey; queryFactory.delete(survey) .where(survey.name.eq("XXX")) .execute();没有
where:queryFactory.delete(survey).execute()2.3.18. DML 语句批量操作
Querydsl SQL 通用调用 DML API来使用JDBC批处理更新操作。如果有一系列相似结构的操作,可以调用
addBatch()方法将多个操作绑定到同一个DMLClause中来进行统一调用。下面有几个例子,说明了UPDATE,INSERT和DELETE的批量操作。Update:
QSurvey survey = QSurvey.survey; queryFactory.insert(survey).values(2, "A").execute(); queryFactory.insert(survey).values(3, "B").execute(); SQLUpdateClause update = queryFactory.update(survey); update.set(survey.name, "AA").where(survey.name.eq("A")).addBatch(); update.set(survey.name, "BB").where(survey.name.eq("B")).addBatch();Delete:
queryFactory.insert(survey).values(2, "A").execute(); queryFactory.insert(survey).values(3, "B").execute(); SQLDeleteClause delete = queryFactory.delete(survey); delete.where(survey.name.eq("A")).addBatch(); delete.where(survey.name.eq("B")).addBatch(); assertEquals(2, delete.execute());Insert:
SQLInsertClause insert = queryFactory.insert(survey); insert.set(survey.id, 5).set(survey.name, "5").addBatch(); insert.set(survey.id, 6).set(survey.name, "6").addBatch(); assertEquals(2, insert.execute());2.3.19. 生成Bean类
使用
MetaDataExporter类来生成数据库表对应的 JavaBean 类作为DTOjava.sql.Connection conn = ...; MetaDataExporter exporter = new MetaDataExporter(); exporter.setPackageName("com.myproject.mydomain"); exporter.setTargetFolder(new File("src/main/java")); exporter.setBeanSerializer(new BeanSerializer()); exporter.export(conn.getMetaData());现在,可以使用
DMLClause中使用Bean和查询对象Bean作为参数,直接进行查询了:QEmployee e = new QEmployee("e"); // Insert Employee employee = new Employee(); employee.setFirstname("John"); Integer id = queryFactory.insert(e).populate(employee).executeWithKey(e.id); employee.setId(id); // Update employee.setLastname("Smith"); assertEquals(1l, queryFactory.update(e).populate(employee) .where(e.id.eq(employee.getId())).execute()); // Query Employee smith = queryFactory.selectFrom(e).where(e.lastname.eq("Smith")).fetchOne(); assertEquals("John", smith.getFirstname()); // Delete assertEquals(1l, queryFactory.delete(e).where(e.id.eq(employee.getId())).execute());2.3.20. 提取SQL语句和查询绑定
SQLBindings bindings = query.getSQL(); System.out.println(bindings.getSQL());如果想在打印的SQL语句中看到绑定的常量参数,可以调用配置类
Configuration的setUseLiterals(true)方法。2.3.21. 自定义类型
Querydsl SQL 提供了自定义类型与
ResultSet/Statement映射的可能性。自定义类型可以在配置中进行注册,在构建查询(SQLQuery)时,Configuration将作为参数被自动添加至查询中。Configuration configuration = new Configuration(new H2Templates()); // overrides the mapping for Types.DATE configuration.register(new UtilDateType());具体映射至数据库表中的列:
Configuration configuration = new Configuration(new H2Templates()); // declares a mapping for the gender column in the person table configuration.register("person", "gender", new EnumByNameType<Gender>(Gender.class));如果要自定义数值映射,可以调用
registerNumeric方法:configuration.registerNumeric(5,2,Float.class);上述调用,将
NUMERIC(5,2)映射为java.lang.Float类型。2.3.22. 查询和更新监听器
SQLListener是一个用于查询和执行 DML语句的监听器接口。可以通过addListener方法将SQLListener实例注册到Configuration配置中,或者注册到 query/clause 级别的查询或操作语句中。监听器实现可用于数据同步,缓存,日志记录和数据校验等。
2.3.23. Spring 框架整合
Querydsl SQL 可以通过
querydsl-sql-spring模块来整合 spring 框架。<dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql-spring</artifactId> <version>${querydsl.version}</version> </dependency>它提供了一个 Spring 异常转换器,和一个 Spring 数据库连接提供者实现,用于支持 Spring 框架中的事务管理。
package com.querydsl.example.config; import com.querydsl.sql.H2Templates; import com.querydsl.sql.SQLQueryFactory; import com.querydsl.sql.SQLTemplates; import com.querydsl.sql.spring.SpringConnectionProvider; import com.querydsl.sql.spring.SpringExceptionTranslator; import com.querydsl.sql.types.DateTimeType; import com.querydsl.sql.types.LocalDateType; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.inject.Inject; import javax.inject.Provider; import javax.sql.DataSource; import java.sql.Connection; @Configuration public class JdbcConfiguration { @Bean public DataSource dataSource() { // implementation omitted } @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean public com.querydsl.sql.Configuration querydslConfiguration() { //change to your Templates SQLTemplates templates = H2Templates.builder().build(); com.querydsl.sql.Configuration configuration = new com.querydsl.sql.Configuration(templates); configuration.setExceptionTranslator(new SpringExceptionTranslator()); return configuration; } @Bean public SQLQueryFactory queryFactory() { Provider<Connection> provider = new SpringConnectionProvider(dataSource()); return new SQLQueryFactory(querydslConfiguration(), provider); } }译者注:翻译文档时的版本 v4.0.7 中,
querydsl-sql-spring中的SpringConnectionProvider中返回的Connection必须在事务开启且进行事务时才能获取,否则将抛出异常。

浙公网安备 33010602011771号