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.Querycom.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.0
    

    2.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 desc
    

    2.1.10. 分組(Grouping)

    下面的是對結果分組:

    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-style 方式定義要更新的屬性,execute 調用指行更新操作並返回被更新的實體的數量。

    JPA中的DML語句並不考慮JPA級聯規則,也不提供細粒度二級緩存的交互。

    2.1.13. 子查詢

    使用 JPAExpressions 的靜態工廠方法創建一個子查詢,並且通過調用 fromwhere 等定義查詢參數。

    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. 參數說明

    名稱 說明
    jdbcDriver JDBC 驅動支持的類名
    jdbcUrl JDBC url
    jdbcUser JDBC user
    jdbcPassword JDBC password
    namePrefix 生成查詢類類名的前綴,默認:"Q"
    nameSuffix 生成查詢類類名的后綴,默認:""
    beanPrefix 生成的Bean類類名的前綴
    beanSuffix 生成的Bean類類名的前綴
    packageName 生成的源文件的包名稱
    beanPackageName 生成的Bean文件的包名稱,默認:packageName
    beanInterfaces 生成的Bean類實現的一組接口的類全名,默認:""
    beanAddToString 設置true時,生成的類中將自動添加toString()方法的實現,默認:false
    beanAddFullConstructor 設置true時,除了原始的默認構造方法外,還額外生成一個全參數完整的構造方法,默認:false
    beanPrintSupertype 設置true時,將同時打印出超類,默認:false
    schemaPattern 使用LIKE模式中的schema名稱模式;它必須匹配數據庫存儲中的schema名稱
    tableNamePattern 使用LIKE模式中表名稱模式;它必須匹配數據庫中的表名稱,多個模式使用逗號,分隔,默認:null
    targetFolder 生成源文件的目標目錄
    beansTargetFolder 生成的Bean源文件的目標目錄,默認與 targetFolder 設置的目錄相同
    namingStrategyClass 命名策略的實現類類名,默認:DefaultNamingStrategy
    beanSerializerClass Bean序列化的實現類類名,默認:BeanSerializer
    serializerClass 序列化實現類類名,默認:MetaDataSerializer
    exportBeans 設置為true時,同時導出Bean類,請參與第 2.14.13 部份,默認:false
    innerClassesForKeys 設置為true時,會為主鍵生成一個內部類,默認:false
    validationAnnotations 設置為true時,序列化時將加入validation驗證標簽,默認:false
    columnAnnotations 導出列標簽,默認:false
    createScalaSources 是否導出Scala源文件,而不是Java源文件,默認:false
    schemaToPackage 追加schema名稱至包名稱,默認:false
    lowerCase 是否轉換為小寫名稱,默認:false
    exportTables 導出數據表,默認:true
    exportViews 導出視圖,默認:true
    exportPrimaryKeys 導出主鍵,默認:true
    tableTypesToExport 導出使用逗號分隔的表類型的列表(可能的值取決於JDBC驅動)。允許導出任意的類型,例如:TABLE,MATERIALIZED VIEW。如果此值被設置,那么 exportTablesexportViews的設置將會無效
    exportForeignKeys 是否導出外鍵設置,默認:true
    customTypes 用戶自定義類型,默認:none
    typeMappings table.column 和 JavaType的映射設置,默認:none
    numericMappings size/digits 和 JavaType 的映射設置,默認:none
    imports 生成的查詢類中要引入的一組類: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.mydomain JAVA包目錄中。 生成的查詢類中,下划線連接的表名使用駝峰式命名作為查詢類類名(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語句,而不是使用參數式綁定的預編譯語句,並且覆蓋 schematable和自定義的類型,具體的說明請參見 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 ASC
    

    2.3.10. 分組查詢

    通過下面的方式進行分組查詢

    queryFactory.select(customer.lastName)
            .from(customer)
            .groupBy(customer.lastName)
            .fetch();
    

    上面的代碼等效於下面的SQL語句:

    SELECT c.last_name
    FROM customer c
    GROUP BY c.last_name
    

    2.3.11. 使用子查詢

    要創建一個子查詢,可以使用 SQLExpressions 的工廠方法,可以通過 fromwhere 等添加查詢參數

    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 中來進行統一調用。下面有幾個例子,說明了 UPDATEINSERT 和 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 類作為 DTO

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM