51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Spring Boot 整合 QueryDSL 及常见用法

QueryDSL 是一个用于构建类型安全查询的开源 Java ORM 框架。它提供了一种 Fluent 风格的 API 来构建和执行数据库查询,并提供了编译时类型检查,以避免常见的查询错误。QueryDSL 支持多种数据库,包括关系型数据库和 NoSQL 数据库,可以与多个持久化框架(如 JPAHibernate 等)整合使用。它简化了查询的编写过程,使得查询代码更易于理解、维护和重用。

QueryDSL 在 Spring Boot 中通常配合 Spring Data JPA 使用,它会根据定义的 JPA Entity 类自动生成对应的查询类。通过查询类,除了可以快速地进行基本的 CRUD 操作外还支持 JOINGROUP、子查询等复杂的检索。而这一切都无需编写任何 SQL 语句,代码即 SQL。

本文将会带你了解如何在 Spring Boot 中整合 QueryDSL + Spring Data JPA,以及 QueryDSL 的常见用法。

示例项目 {#示例项目}

本文使用到的软件版本:

  • Java:21
  • Spring Boot:3.2.0
  • MySQL:8.0.26

添加依赖 {#添加依赖}

创建 Spring Boot 应用,完整的 pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> <relativePath /> <!-- lookup parent from repository --> </parent>

<groupId>springdoc</groupId> <artifactId>springdoc-demo</artifactId> <version>0.0.1-SNAPSHOT</version>

<name>springdoc-demo</name>

<description>Demo project for Spring Boot</description>

<properties> <java.version>21</java.version> </properties>

<repositories> <repository> <id>huaweicloud</id> <url>https://mirrors.huaweicloud.com/repository/maven/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories>

<dependencies>

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

<!-- MYSQL 驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency>

<!-- JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>

<!-- QueryDSL JPA --> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <version>${querydsl.version}</version> <classifier>jakarta</classifier> </dependency>

<!-- QueryDSL APT --> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>${querydsl.version}</version> <classifier>jakarta</classifier> <scope>provided</scope> </dependency> </dependencies>

<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <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>

QueryDSL 的版本也受到 Spring Boot 的管理,因此在这里只需要将其版本号声明为 ${querydsl.version} 即可。

apt-maven-plugin Maven 插件的作用是根据 JPA Entity 生成查询类,其中 <outputDirectory> 节点指定了存放查询类的目录,而 <processor> 节点的配置则表示根据 JPA 注解生成查询类。

配置文件 {#配置文件}

application.yaml 中定义数据源和 JPA 等配置信息:

logging:
  level:
    "ROOT": INFO
    # 输出 SQL 绑定参数
    "org.hibernate.orm.jdbc.bind": TRACE

spring:

数据源

datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true username: root password: root

jpa: # 输出 SQL show-sql: true properties: # 格式化输出的 SQL "hibernate.format_sql": true

如上,除了基本的数据源配置外。还配置了 org.hibernate.orm.jdbc.bind Logger 的日志级别为 TRACE,用于在日志中输出 SQL 绑定的参数。

Entity {#entity}

定义一个 User 类,表示用户:

package cn.springdoc.demo.entity;

import java.time.LocalDateTime;

import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table;

@Entity @Table(name = "t_user") public class User {

// ID @Id @Column @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id;

// 名称 @Column private String name;

// 是否启用 @Column private Boolean enabled;

// 创建时间 @Column private LocalDateTime createAt;

// get、set、toString 方法省略 }

创建 Car 类,表示用户所拥有的汽车:

package cn.springdoc.demo.entity;

import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table;

@Entity @Table(name = "t_car") public class Car {

// ID @Id @Column @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id;

// 汽车名称 @Column private String name;

// 用户 ID @Column private Integer userId;

// get、set、toString 方法省略 }

如上,使用 @Entity 注解表示该类是一个 JPA 实体类。该注解很重要,因为 QueryDSL 会根据此生成对应的查询类。

通过 @Table 注解指定表名称,通过 @Id 注解指定 ID 列,通过 @GeneratedValue 注解指定 ID 生成策略,这里使用的是 GenerationType.IDENTITY 即,使用数据库自增。通过 @Column 指定普通列。这都是很常见的 JPA 注解用法,这里不多解释。

一个 User 可以有多个 Car,这两个实体表示了一个一对多的关系。

对应的表结构如下:

-- User
CREATE TABLE `t_user` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `enabled` tinyint unsigned NOT NULL COMMENT '是否启用。0:禁用,1:启用',
  `name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '名字',
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户';

-- Car CREATE TABLE t_car ( id int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', name varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '汽车名称', user_id int NOT NULL COMMENT '用户ID', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='汽车';

Repository {#repository}

定义实体对应的 Repository 接口。

UserRepository

package cn.springdoc.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.querydsl.QuerydslPredicateExecutor;

import cn.springdoc.demo.entity.User;

public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor <User>, QuerydslPredicateExecutor<User> {

}

CarRepository

package cn.springdoc.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.querydsl.QuerydslPredicateExecutor;

import cn.springdoc.demo.entity.Car;

public interface CarRepository extends JpaRepository<Car, Integer>, JpaSpecificationExecutor <Car>, QuerydslPredicateExecutor<Car> {

}

除了基本的 JpaRepositoryJpaSpecificationExecutor 接口外,Spring Data 还为 QueryDSL 提供了 QuerydslPredicateExecutor 接口,预定义了一些快捷的 CRUD 方法。

Application {#application}

最后,在 Application 类上添加 @EntityScan@EnableJpaRepositories 注解,指定实体类和 Repository 接口所在的包。

package cn.springdoc.demo;

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication @EntityScan("cn.springdoc.demo.entity") @EnableJpaRepositories("cn.springdoc.demo.repository") public class DemoApplication {

public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

至此,Spring Data JPA 和 QueryDSL 就整合完毕了。

你如果想了解整合 Spring Data JPA 的更多细节,你可以参考 这篇文章

使用 QueryDSL {#使用-querydsl}

当你在项目中定义了实体类后,QueryDSL 其实已经偷偷为你生成了对应的查询类。

根据 pom.xmlapt-maven-plugin 插件的 <outputDirectory> 配置,查询类存放在 target/generated-sources/java 目录下。

本例中生成的查询类如下:

QueryDSL 生成的查询类

你可以看到,查询类以 Q 开头,名称对应实体类的名称。且包名和实体类所在包一样。最后打包的时候,这些查询类都会被打包到实体所在的包中。

在实际开发中,不建议修改自动生成的这些查询类。也不建议纳入版本控制,只要 Maven 配置正确,它们会自动生成。

自动生成的查询类,你不需要去研究太多。你只需要理解查询类中的每个字段,都对应了实体类以及数据表中的字段即可。

创建初始数据 {#创建初始数据}

QueyDSL 并未提供 INSERT 操作,我们需要依赖 Repository 的 save() 方法来保存实体。

插入测试数据:

package cn.springdoc.demo.test;

import java.time.LocalDateTime;

import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional;

import cn.springdoc.demo.entity.Car; import cn.springdoc.demo.entity.User; import cn.springdoc.demo.repository.CarRepository; import cn.springdoc.demo.repository.UserRepository;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class DemoApplicationTests {

static final Logger log = LoggerFactory.getLogger(DemoApplicationTests.class);

@Autowired UserRepository userRepository;

@Autowired CarRepository carRepository;

@Test @Transactional @Rollback(false) public void test() throws Exception {

// 初始化 3 个用户

User u1 = new User(null, "刘备", Boolean.TRUE, LocalDateTime.now()); User u2 = new User(null, "张飞", Boolean.TRUE, LocalDateTime.now()); User u3 = new User(null, "关羽", Boolean.TRUE, LocalDateTime.now());

this.userRepository.save(u1); this.userRepository.save(u2); this.userRepository.save(u3);

// 给每个用户初始化 2 个 Car this.carRepository.save(new Car(null, "宝马", u1.getId())); this.carRepository.save(new Car(null, "奔驰", u1.getId())); this.carRepository.save(new Car(null, "五菱", u2.getId())); this.carRepository.save(new Car(null, "宝马", u2.getId())); this.carRepository.save(new Car(null, "五菱", u3.getId())); this.carRepository.save(new Car(null, "宝马", u3.getId())); } }

如上,创建了 3 个 User 对象,并给每个 User 创建了 2 个关联的 Car 对象。

执行测试。最终,数据表中的数据如下:

t_user

| id | create_at | enabled | name | |----|---------------------|---------|------| | 1 | 2023-12-10 16:01:55 | 1 | 刘备 | | 2 | 2023-12-10 16:01:55 | 1 | 张飞 | | 3 | 2023-12-10 16:01:55 | 1 | 关羽 |

t_cat

| id | name | user_id | |----|------|---------| | 1 | 宝马 | 1 | | 2 | 奔驰 | 1 | | 3 | 五菱 | 2 | | 4 | 宝马 | 2 | | 5 | 五菱 | 3 | | 6 | 宝马 | 4 |

基本的检索 {#基本的检索}

检索单条记录和检索记录列表:

 // 注入 EntityManager
 @PersistenceContext
 EntityManager entityManager;

@Test @Transactional @Rollback(false) public void test() throws Exception {

// 通过 EntityManager 创建 JPAQueryFactory 实例 var query = new JPAQueryFactory(entityManager);

QUser USER = QUser.user;

// 根据 id 和 enabled 检索单条记录 User user = query.selectFrom(USER).where(USER.id.eq(1).and(USER.enabled.eq(true))).fetchOne(); log.info("user={}", user);

// 检索集合 List<User> users = query.selectFrom(USER).where(USER.enabled.eq(true)).fetch(); log.info("users={}", users); }

输出日志如下:

Hibernate: 
    select
        u1_0.id,
        u1_0.create_at,
        u1_0.enabled,
        u1_0.name 
    from
        t_user u1_0 
    where
        u1_0.id=? 
        and u1_0.enabled=?
TRACE 7028 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:INTEGER) <- [1]
TRACE 7028 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:BOOLEAN) <- [true]
 INFO 7028 --- [           main] c.s.demo.test.DemoApplicationTests       : user=User [id=1, name=刘备, enabled=true, createAt=2023-12-10T16:01:55]
Hibernate: 
    select
        u1_0.id,
        u1_0.create_at,
        u1_0.enabled,
        u1_0.name 
    from
        t_user u1_0 
    where
        u1_0.enabled=?
TRACE 7028 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:BOOLEAN) <- [true]
 INFO 7028 --- [           main] c.s.demo.test.DemoApplicationTests       : users=[User [id=1, name=刘备, enabled=true, createAt=2023-12-10T16:01:55], User [id=2, name=张飞, enabled=true, createAt=2023-12-10T16:01:55], User [id=3, name=关羽, enabled=true, createAt=2023-12-10T16:01:55]]

分页和排序 {#分页和排序}

 // 注入 EntityManager
 @PersistenceContext
 EntityManager entityManager;

@Test @Transactional @Rollback(false) public void test() throws Exception {

// 通过 EntityManager 创建 JPAQueryFactory 实例 var query = new JPAQueryFactory(entityManager);

QUser USER = QUser.user;

// 总记录数量 Long count = query.select(USER.id.count()).from(USER).fetchOne(); log.info("count={}", count);

// 分页 & 排序 List<User> users = query.selectFrom(USER).offset(0).limit(2).orderBy(USER.createAt.desc(), USER.id.asc()).fetch(); log.info("users={}", users); }

执行日志如下:

Hibernate: 
    select
        count(u1_0.id) 
    from
        t_user u1_0
 INFO 13836 --- [           main] c.s.demo.test.DemoApplicationTests       : count=3
Hibernate: 
    select
        u1_0.id,
        u1_0.create_at,
        u1_0.enabled,
        u1_0.name 
    from
        t_user u1_0 
    order by
        u1_0.create_at desc,
        u1_0.id 
    limit
        ?, ?
TRACE 13836 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:INTEGER) <- [0]
TRACE 13836 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:INTEGER) <- [2]
 INFO 13836 --- [           main] c.s.demo.test.DemoApplicationTests       : users=[User [id=1, name=刘备, enabled=true, createAt=2023-12-10T16:01:55], User [id=2, name=张飞, enabled=true, createAt=2023-12-10T16:01:55]]

投影查询 {#投影查询}

仅检索指定的列。

 // 注入 EntityManager
 @PersistenceContext
 EntityManager entityManager;

@Test @Transactional @Rollback(false) public void test() throws Exception {

// 通过 EntityManager 创建 JPAQueryFactory 实例 var query = new JPAQueryFactory(entityManager);

QUser USER = QUser.user;

// 检索单列,单行 String name = query.select(USER.name).from(USER).where(USER.id.eq(1)).fetchOne(); log.info("name={}", name);

// 检索 id 和 name 列,封装结果为 Tuple List<Tuple> tuples = query.select(USER.id, USER.name).from(USER).fetch();

tuples.stream().forEach(tuple -> { log.info("id={}, name={}", tuple.get(USER.id), tuple.get(USER.name)); });

// 检索 id 和 enabled 列,通过 Setter 方法封装为实体 List<User> users = query.select(Projections.bean(User.class, USER.id, USER.enabled)).from(USER).fetch(); log.info("users={}", users); }

执行测试,输出日志如下:

Hibernate: 
    select
        u1_0.name 
    from
        t_user u1_0 
    where
        u1_0.id=?
TRACE 576 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:INTEGER) <- [1]
 INFO 576 --- [           main] c.s.demo.test.DemoApplicationTests       : name=刘备
Hibernate: 
    select
        u1_0.id,
        u1_0.name 
    from
        t_user u1_0
 INFO 576 --- [           main] c.s.demo.test.DemoApplicationTests       : id=3, name=关羽
 INFO 576 --- [           main] c.s.demo.test.DemoApplicationTests       : id=1, name=刘备
 INFO 576 --- [           main] c.s.demo.test.DemoApplicationTests       : id=2, name=张飞
Hibernate: 
    select
        u1_0.id,
        u1_0.enabled 
    from
        t_user u1_0
 INFO 576 --- [           main] c.s.demo.test.DemoApplicationTests       : users=[User [id=1, name=null, enabled=true, createAt=null], User [id=2, name=null, enabled=true, createAt=null], User [id=3, name=null, enabled=true, createAt=null]]

JOIN 查询 {#join-查询}

JOIN 检索关联记录。

 // 注入 EntityManager
 @PersistenceContext
 EntityManager entityManager;

@Test @Transactional @Rollback(false) public void test() throws Exception {

// 通过 EntityManager 创建 JPAQueryFactory 实例 var query = new JPAQueryFactory(entityManager);

QUser USER = QUser.user; QCar CAR = QCar.car;

// INNER JOIN List<Tuple> tuples = query.select(USER.id, USER.name, CAR.name.as("carName")) .from(USER) .innerJoin(CAR).on(CAR.userId.eq(USER.id)) .where(USER.enabled.eq(true)) .fetch() ;

tuples.stream().forEach(tuple -> { log.info("userId={}, name={}, carName={}", tuple.get(USER.id), tuple.get(USER.name), tuple.get(CAR.name.as("carName"))); }); }

执行测试,输出日志如下:

Hibernate: 
    select
        u1_0.id,
        u1_0.name,
        c1_0.name 
    from
        t_user u1_0 
    join
        t_car c1_0 
            on c1_0.user_id=u1_0.id 
    where
        u1_0.enabled=?
TRACE 16652 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:BOOLEAN) <- [true]
 INFO 16652 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=1, name=刘备, carName=宝马
 INFO 16652 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=1, name=刘备, carName=奔驰
 INFO 16652 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=2, name=张飞, carName=五菱
 INFO 16652 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=2, name=张飞, carName=宝马
 INFO 16652 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=3, name=关羽, carName=五菱
 INFO 16652 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=3, name=关羽, carName=宝马

GROUP 查询 {#group-查询}

GROUP 聚合查询。

 // 注入 EntityManager
 @PersistenceContext
 EntityManager entityManager;

@Test @Transactional @Rollback(false) public void test() throws Exception {

// 通过 EntityManager 创建 JPAQueryFactory 实例 var query = new JPAQueryFactory(entityManager);

QCar CAR = QCar.car;

// GROUP List<Tuple> tuples = query.select(CAR.name, CAR.name.count()) .from(CAR) .groupBy(CAR.name) .fetch() ;

tuples.stream().forEach(tuple -> { log.info("carName={}, count={}", tuple.get(CAR.name), tuple.get(CAR.name.count())); }); }

执行测试,输出日志如下:

Hibernate: 
    select
        c1_0.name,
        count(c1_0.name) 
    from
        t_car c1_0 
    group by
        c1_0.name
 INFO 8104 --- [           main] c.s.demo.test.DemoApplicationTests       : carName=宝马, count=3
 INFO 8104 --- [           main] c.s.demo.test.DemoApplicationTests       : carName=奔驰, count=1
 INFO 8104 --- [           main] c.s.demo.test.DemoApplicationTests       : carName=五菱, count=2

子查询 {#子查询}

结果列子查询,以及条件语句中的子查询。

 // 注入 EntityManager
 @PersistenceContext
 EntityManager entityManager;

@Test @Transactional @Rollback(false) public void test() throws Exception {

// 通过 EntityManager 创建 JPAQueryFactory 实例 var query = new JPAQueryFactory(entityManager);

QUser USER = QUser.user; QCar CAR = QCar.car;

// 结果列子查询 List<Tuple> tuples = query.select(CAR.id, CAR.name, JPAExpressions.select(USER.name).from(USER).where(USER.id.eq(CAR.userId))) .from(CAR) .fetch() ; log.info("tuples={}", tuples);

// 条件子查询 List<User> users = query.selectFrom(USER) .where(USER.id.in(JPAExpressions.select(CAR.userId).from(CAR).where(CAR.name.eq("宝马")))) .fetch(); log.info("users={}", users); }

执行测试,输出日志如下:

Hibernate: 
    select
        c1_0.id,
        c1_0.name,
        (select
            u1_0.name 
        from
            t_user u1_0 
        where
            u1_0.id=c1_0.user_id) 
    from
        t_car c1_0
 INFO 18848 --- [           main] c.s.demo.test.DemoApplicationTests       : tuples=[[1, 宝马, 刘备], [2, 奔驰, 刘备], [3, 五菱, 张飞], [4, 宝马, 张飞], [5, 五菱, 关羽], [6, 宝马, 关羽]]
Hibernate: 
    select
        u1_0.id,
        u1_0.create_at,
        u1_0.enabled,
        u1_0.name 
    from
        t_user u1_0 
    where
        u1_0.id in (select
            c1_0.user_id 
        from
            t_car c1_0 
        where
            c1_0.name=?)
TRACE 18848 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:VARCHAR) <- [宝马]
 INFO 18848 --- [           main] c.s.demo.test.DemoApplicationTests       : users=[User [id=1, name=刘备, enabled=true, createAt=2023-12-10T16:01:55], User [id=2, name=张飞, enabled=true, createAt=2023-12-10T16:01:55], User [id=3, name=关羽, enabled=true, createAt=2023-12-10T16:01:55]]

结果集封装 {#结果集封装}

把结果集封装 Map,POJO 等。

 // 注入 EntityManager
 @PersistenceContext
 EntityManager entityManager;

@Test @Transactional @Rollback(false) public void test() throws Exception {

// 通过 EntityManager 创建 JPAQueryFactory 实例 var query = new JPAQueryFactory(entityManager);

QUser USER = QUser.user; QCar CAR = QCar.car;

// 结果集封装为 Map List<Map<Expression<?>, ?>> ret = query.select(Projections.map(USER.id, USER.name, CAR.name)).from(USER) .innerJoin(CAR).on(CAR.userId.eq(USER.id)) .fetch() ;

ret.stream().forEach(item -> { log.info("userId={}, name={}, carName={}", item.get(USER.id), item.get(USER.name), item.get(CAR.name)); });

// 通过字段,封装为 Bean List<User> users = query.select(Projections.fields(User.class, USER.id, USER.name)).from(USER) .where(USER.id.in(1, 2)) .fetch() ; log.info("users={}", users);

// 通过构造函数,封装为 Bean User user = query.select(Projections.constructor(User.class, USER.id, USER.name, USER.enabled, USER.createAt)).from(USER) .fetchFirst(); // fetchFirst 只检索结果集中的第一条记录 log.info("user={}", user); }

执行测试,输出如下:

Hibernate: 
    select
        u1_0.id,
        u1_0.name,
        c1_0.name 
    from
        t_user u1_0 
    join
        t_car c1_0 
            on c1_0.user_id=u1_0.id
 INFO 17724 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=1, name=刘备, carName=宝马
 INFO 17724 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=1, name=刘备, carName=奔驰
 INFO 17724 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=2, name=张飞, carName=五菱
 INFO 17724 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=2, name=张飞, carName=宝马
 INFO 17724 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=3, name=关羽, carName=五菱
 INFO 17724 --- [           main] c.s.demo.test.DemoApplicationTests       : userId=3, name=关羽, carName=宝马
Hibernate: 
    select
        u1_0.id,
        u1_0.name 
    from
        t_user u1_0 
    where
        u1_0.id in (?, ?)
TRACE 17724 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:INTEGER) <- [1]
TRACE 17724 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:INTEGER) <- [2]
 INFO 17724 --- [           main] c.s.demo.test.DemoApplicationTests       : users=[User [id=1, name=刘备, enabled=null, createAt=null], User [id=2, name=张飞, enabled=null, createAt=null]]
Hibernate: 
    select
        u1_0.id,
        u1_0.name,
        u1_0.enabled,
        u1_0.create_at 
    from
        t_user u1_0 
    limit
        ?
TRACE 17724 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:INTEGER) <- [1]
 INFO 17724 --- [           main] c.s.demo.test.DemoApplicationTests       : user=User [id=1, name=刘备, enabled=true, createAt=2023-12-10T16:01:55]

更新数据 {#更新数据}

QueryDSL 提供了更新数据的 API。

 // 注入 EntityManager
 @PersistenceContext
 EntityManager entityManager;

@Test @Transactional @Rollback(false) public void test() throws Exception {

// 通过 EntityManager 创建 JPAQueryFactory 实例 var query = new JPAQueryFactory(entityManager);

QUser USER = QUser.user; QCar CAR = QCar.car;

// 更新字段 long ret = query.update(USER) .set(USER.name, "刘皇叔") .set(USER.enabled, false) .where(USER.id.eq(1)) .execute() ;

log.info("ret={}", ret);

// 子查询 ret = query.update(USER) .set(USER.name, JPAExpressions.select(CAR.name).from(CAR).where(CAR.id.eq(1))) .set(USER.enabled, true) .where(USER.id.eq(8)) .execute() ; log.info("ret={}", ret);

// 自增 ret = query.update(USER) .set(USER.id, USER.id.add(-1)) .set(USER.enabled, true) .where(USER.id.eq(9)) .execute() ; }

执行测试,输出如下:

Hibernate: 
    update
        t_user 
    set
        name=?,
        enabled=? 
    where
        id=?
TRACE 13392 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:VARCHAR) <- [刘皇叔]
TRACE 13392 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:BOOLEAN) <- [false]
TRACE 13392 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (3:INTEGER) <- [1]
 INFO 13392 --- [           main] c.s.demo.test.DemoApplicationTests       : ret=1
Hibernate: 
    update
        t_user 
    set
        name=(select
            c1_0.name 
        from
            t_car c1_0 
        where
            c1_0.id=?),
        enabled=? 
    where
        id=?
TRACE 13392 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:INTEGER) <- [1]
TRACE 13392 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:BOOLEAN) <- [true]
TRACE 13392 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (3:INTEGER) <- [8]
 INFO 13392 --- [           main] c.s.demo.test.DemoApplicationTests       : ret=0
Hibernate: 
    update
        t_user 
    set
        id=(id+?),
        enabled=? 
    where
        id=?
TRACE 13392 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:INTEGER) <- [-1]
TRACE 13392 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:BOOLEAN) <- [true]
TRACE 13392 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (3:INTEGER) <- [9]

注意 ,本例中的 UPDATE 语句是不合理的(例如,ID 自增),这么写纯粹是为了演示 QueryDSL 的更新功能。

最后 {#最后}

QueryDSL 的用法总结:通过 EntityManager 创建 JPAQueryFactory 实例。然后使用查询类完成一系列的 CRUD 操作。

上述示例代码,基本上足够日常开发,但是如果业务中涉及的查询确实比较复杂,那么你可以考虑使用 Spring 6 中的 JdbcClient 来进行查询。

总结 {#总结}

本文介绍了如何在 Spring Boot 中整合 QueryDSL,以及 QueryDSL 的日常使用示例。

QueryDSL 是一款开发效率、灵活性、表达能力都非常高的 ORM。不用像 MyBatis 那样需要在 XML 中定义 SQL,再在 Mapper 接口中定义查询方法。

赞(5)
未经允许不得转载:工具盒子 » Spring Boot 整合 QueryDSL 及常见用法