SpringData JPA 大家应该已经比较熟悉了,它简化了 JPA 的方法, 使得我们可以快速的操作数据库。在一些复杂的业务中,比如分页、条件查询等,SpringData JPA 为我们封装了一些列的类和方法(Specification
、Pageable
),不需要写 SQL,以一种面向对象的方式进行数据的查询。
而 QueryDSL 则是在 SpringData JPA 的基础上,更近一步,提供了统一的类型安全的查询方式,正如标题所说的,它更面向对象了。QueryDSL 由一些查询方法构成,熟悉 SpringData JPA 基本上可以立刻上手去 coding。本文就讲讲它的一些用法,已经部分由于 SpringData JPA 的查询方式。
引入
QueryDSL 有多个模块,可以支持 JPA、Mongodb、Lucene 等,这里我们用到的 module 是 JPA。
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
<!-- 代码生成插件配置 -->
<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>
为了满足 QueryDSL 查询的规范,我们需要为每一个持久化类生成一个对应的 EntityPath<T>
,这个过程可通过 maven 插件自动生成。添加该插件,执行 maven compile
后,会在 entity 同名的包下生成 Q 开头的类。
配置
使用 QueryDSL 有两种方式。
- 一种是注入一个
JPAQueryFactory
对象,通过它来执行各种查询操作,可类比JdbcTemplate
。
@Configuration
public class QuerydslConfig {
@Bean
public JPAQueryFactory jpaQuery(EntityManager entityManager) {
return new JPAQueryFactory(entityManager);
}
}
- 另一种是让 repository 实现实现
QuerydslPredicateExecutor<T>
接口,可类比JpaSpecificationExecutor<T>
的用法。
@NoRepositoryBean
public interface IBaseRepository<T, PK extends Serializable> extends
JpaRepository<T, PK>, JpaSpecificationExecutor<T>, QuerydslPredicateExecutor<T> {
}
用法
// 基本用法
QCompany qc = QCompany.company;
queryFactory.update(qc).where(qc.id.eq(1)).set(qc.createdTime, LocalDateTime.now()).execute();
queryFactory.delete(qc).where(qc.createdTime.after(LocalDateTime.now().minusDays(5))).execute();
Company c = queryFactory.selectFrom(qc)
.where(qc.createdTime.eq(LocalDateTime.now())
.and(qc.companyName.like("%aa%"))).fetchOne();
QueryDSL 支持丰富的查询方法,具体可以 参考最后的资料。基本的操作如上,可以看到一些 sql 中的操作均通过 java 方法的形式进行调用,可以时说是将面向对象进行到底了。下面部分例子来自博客 《SpringBoot 环境下 QueryDSL-JPA 的入门及进阶》
查询条件
List<MemberDomain> memberConditionList = queryFactory.selectFrom(qm)
.where(qm.name.like('%'+"Jack"+'%')
.and(qm.address.contains("厦门"))
.and(qm.status.eq("0013"))
.and(qm.age.between(20, 30)))
.and(qm.state.in(1,2))
.fetch();
结果封装
QMemberDomain qm = QMemberDomain.memberDomain;
// 查询字段 - select()
List<String> nameList = queryFactory.select(qm.name).from(qm).fetch();
// 查询实体 - selectFrom()
List<MemberDomain> memberList = queryFactory.selectFrom(qm).fetch();
// 查询并将结果封装至 dto 中
List<MemberVo> dtoList = queryFactory.select(Projections.constructor(MemberVo.class,qm.name,qm.address)).from(qm).fetch();
控制查询结果的条数
// 去重查询 - selectDistinct()
List<String> distinctNameList = queryFactory.selectDistinct(qm.name).from(qm).fetch();
// 获取首个查询结果 - fetchFirst()
MemberDomain firstMember = queryFactory.selectFrom(qm).fetchFirst();
// 获取唯一查询结果 - fetchOne()
// 当 fetchOne() 根据查询条件从数据库中查询到多条匹配数据时,会抛 `NonUniqueResultException`。
MemberDomain anotherFirstMember = queryFactory.selectFrom(qm).fetchOne();
条件查询 + 投影
条件查询在 JPA 中一般需要自己拼接 sql,或者使用 Specification。而 Specification 只能将所有数据都查出来,无法实现投影。那么在 QueryDSL 中就灵活很多,而且代码也比较容易理解。
BooleanBuilder builder = new BooleanBuilder();
//like
builder.and(qm.name.like('%'+"Jack"+'%'));
//contain
builder.and(qm.address.contains("厦门"));
//equal 示例
builder.and(qm.status.eq("0013"));
//between
builder.and(qm.age.between(20, 30));
List<MemberVo> dtoList = queryFactory.select(Projections.constructor(MemberVo.class,qm.name,qm.address))
.from(qm).where(builder).fetch();
只需要构建一个 BooleanBuilder
就可以了,相比之前的使用 Specification 而写的匿名内部类要简单很多。
其他查询
// 左连接
QTransaction qt = QTransaction.transaction;
QContract qc = QContract.contract1;
List<Transaction> list = queryFactory.selectFrom(qt).leftJoin(qc).on(qt.contractId.eq(qc.id)).fetch();
// 子查询
QOrgInfo qo = QOrgInfo.orgInfo;
QOrgUser qou = QOrgUser.orgUser;
List<String> list = jpaQueryHZ.select(qo.leaderEmail).from(qo)
.where(qo.id.in(JPAExpressions.select(qou.tid).from(qou).where(qou.email.in(members))))
.fetch();
排序、分页
QMeeting qm = QMeeting.meeting;
Pageable pageable = PageRequest.of(param.getPageIndex() - 1, param.getPageSize());
QueryResults<Meeting> results = jpaQueryHZ.selectFrom(qm).where(builder)
.orderBy(qm.meetingDate.desc())
.orderBy(qm.meetingStart.desc())
.offset(pageable.getOffset()).limit(pageable.getPageSize())
.fetchResults();
List<MeetingVO> list = results.getResults();
总结
本文主要简单介绍了一下 QueryDSL 的一些用法,他可以搭配 JPA 一起使用,在可读性和编码效率上能有不少的提高,尤其是在一些相对比较复杂的查询下,例如条件查询,表连接等。