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必須在事務開啟且進行事務時才能獲取,否則將拋出異常。
