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