分类
persistence

JPA Querydsl 入门

A Guide to Querydsl with JPA

1. 概述

Querydsl是一款开源的JAVA框架,用于构造安全的SQL查询语句,在使用的风格上更贴近于SQL。本文将讨论其集成在JPA中的使用方法。Querydsl起初仅提供了对Hibernate中的HQL的支持,但如今其已经全面支持JPA, JDO, JDBC, Lucene, Hibernate Search, MongoDB, Collections 以及 RDFBean 了。

2. 准备

 在Spring Boot项目中编辑pom.xml文件,添加以下信息:

<properties>
	...
	<querydsl.version>4.1.3</querydsl.version>
</properties>
<dependencies>
        ...

	<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>
</dependencies>
<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>

如上所示,我们使用的Querdsl版本为4.1.3。添加了两个依赖以及一个MAVEN APT插件。该插件中声明的com.querydsl.apt.jpa.JPAAnnotationProcessor的作用是:查找以 javax.persistence.Entity注解的类,并自动生成其对应的用于Querdsl查询的类。

3. 小试牛刀

Querdsl根据实体来构造查询类型,然后在查询的过程中直接应用查询类型的属性,从而保证了在编译的阶段便能够发现其拼写错误,被认为是安全的。

相较于JPA Criteria Queries中在查询时指定字符串而造成不安全的特性,Querdsl在安全性上更加具有优势。

同时,Querdsl在查询时也保持了较高的一致性。下面,让我们分步查看Querds是如何工作的。

3.1 建立实体

新建学生Student实体如下:

/**
 * 学生
 */
@Entity
public class Student {
    @Id
    @GeneratedValue
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 学号
     */
    private String no;

    /**
     * 体重
     */
    private Integer weight;

  
    // setter and getter
}

此时执行mvn compile,将在target文件夹对应的包中对应生成供查询使用的QStudent类。

我们可以通过获取QStudent中的静态变量student来获取一个QStudent的实例:

QStudent qStudent = QStudent.student;

如果你使用的是IDEA编辑器,可能会接收到找不到类的错误,此时可以尝试找到pom.xml,点击右键 -> maven -> Generate Sources and Update Folders。

3.2 使用JPAQueryFactory构建查询

我们可以如下代码来获取一个JPAQueryFactory实例:

JPAQueryFactory query = new JPAQueryFactory(entityManager);

注意:上述代码中的entityManager是由JPA中的EntityManager

假设需要查找姓名为X的学生,则代码为:

/**
 * 查询姓名为X的学生
 * @param name 学生姓名
 * @return
 */
Student findByName(String name) {
    JPAQueryFactory query = new JPAQueryFactory(entityManager);

    QStudent qStudent = QStudent.student;

    return query.selectFrom(qStudent)
         .where(qStudent.name.eq(name))
         .fetchOne();
}

上述代码中:selectFrom指定了查询的数据表为student,where()方法规定的查询的条件,fetchOne()返回相关的某条学生记录。

多条件组件查询示例:

query.selectFrom(qStudent)
                .where(qStudent.name.eq(name), qStudent.no.eq(no))
                .fetchOne();

where()方法可能接收多个表达式(查询条件),在查询过程中对多个表达式(查询条件)做组合查询。此外,也可以调用表达式的and方法来完成组合查询:

query.selectFrom(qStudent)
                    .where(qStudent.name.eq(name).and(qStudent.no.eq(no)))
                    .fetchOne();

如果将上述代码转换为JPQL语言,则相当于:

select student form Student as student where student.name = ?1 and student.no = ?2

此外,还支持or查询:

/**
 * 按姓名或学号查询学生
 * 满足任一条件即可
 *
 * @param name 姓名
 * @param no   学号
 * @return
 */
List<Student> findByNameOrNo(String name, String no) {
    JPAQueryFactory query = new JPAQueryFactory(entityManager);
    QStudent qStudent = QStudent.student;

    return query.selectFrom(qStudent)
                .where(qStudent.name.eq(name).or(qStudent.no.eq(no)))
                .fetch();
}

4. 排序及聚合查询

4.1 排序

使用orderBy方法来实现按指定字段排序:

/**
 * 按体重排序
 *
 * @return
 */
List<Student> findAllOrderByWeight() {
    JPAQueryFactory query = new JPAQueryFactory(entityManager);
    QStudent qStudent = QStudent.student;

    return query.selectFrom(qStudent)
                .orderBy(qStudent.weight.asc())
                .fetch();
}

4.2 聚合

querydsl支持Sum, Avg, Max, Min等聚合函数,比如查询出所有学生中最大的体重:

int maxWeight = query.selectFrom(qStudent)
            .select(qStudent.weight.max())
            .fetchFirst();

4.3 GroupBy

当然也支持groupBy,比如接学生的体重进行分类:

query.select(qStudent.weight)
                    .from(qStudent)
                    .groupBy(qStudent.weight)
                    .fetch();

5. 单元测试

单元测试是保证代码正确的高效有力的保障手段,为此我们使用data.sql创建了4个学生,并新建如下单元测试进行验证:

package club.codedemo.querydslwithjpatutorial;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@SpringBootTest
class StudentDaoTest {

    @Autowired
    StudentDao studentDao;

    @Test
    void findByName() {
        Assertions.assertEquals(1L,
                this.studentDao.findByName("zhangsan").getId());
    }

    @Test
    void findByNameAndNo() {
        Assertions.assertEquals(1L,
                this.studentDao.findByNameAndNo("zhangsan", "200001").getId());
    }

    @Test
    void findByNameAndNo1() {
        Assertions.assertEquals(1L,
                this.studentDao.findByNameAndNo1("zhangsan", "200001").getId());
    }

    @Test
    void findByNameOrNo() {
        Assertions.assertEquals(2,
                this.studentDao.findByNameOrNo("zhangsan", "200002").size());
    }

    @Test
    void findAllOrderByWeight() {
        List<Student> students = this.studentDao.findAllOrderByWeight();

        Assertions.assertEquals(56, students.get(0).getWeight());
        Assertions.assertEquals(65, students.get(students.size() - 1).getWeight());
    }

    @Test
    void getMaxWeight() {
        Assertions.assertEquals(65, this.studentDao.getMaxWeight());
    }

    @Test
    void groupByWeight() {
        Assertions.assertEquals(3,
                this.studentDao.groupByWeight().size());
    }
}

6. 总结

本文对Spring Boot JPA中使用Querydsl进行了初步介绍。Querydsl类型安全、语法简短、贴近于SQL的特点,使其很好做为JPA查询的一种补充。更多用户请参考Querydsl简介