简介
JPA 全称 Java Persistence API,中文意为Java持久层API。JPA并不是一个框架,而是一类框架的统称,比如早期的Hibernate,都是ORM框架,用于在关系型数据库和业务实体对象之间作映射,我们可以通过 JPA 所提供的默认的方法来快速实现CRUD操作。
而QueryDSL 则是在 SpringDataJPA 基础上更进一步,更面向对象,可以说是为 JPA 插上了翅膀。
本文将介绍如何通过 SpringDataJPA + QueryDSL 实现一些诸如子查询、动态 SQL 的复杂操作。
1.手动嵌套 Mysql 函数
一般情况下,如果想嵌套 MySQL 函数或者执行更复杂的查询逻辑,可以利用 Querydsl 的表达式语言来构建动态查询。QueryDSL 提供了一个强大的 API 来编写类型安全的查询,同时还可以灵活地组合 SQL 函数。
例如:要查询表 test01 中 name 字段在 “张三,李四,王五” 中的数据。
select * from test01 t where FIND_IN_SET(t.name, "张三,李四,王五") > 0;
这里可以使用 Expressions.stringTemplate 来创建布尔表达式代表 MySQL 的 FIND_IN_SET 函数。
public List<Tuple> findRecordsByNameList(String nameList) {
JPAQuery<Tuple> jpaQuery = jpaQueryFactory.select(
qdo.id,qdo.name
).from(qdo);
StringTemplate stringTemplate = Expressions.stringTemplate("FIND_IN_SET({0},{1})", qdo.name,nameList);
return jpaQuery.where(stringTemplate.stringValue().gt("")).fetch();
}
2.动态 SQL
在 MyBatis 中,我们可以通过使用 <if>、<when> 和 <foreach> 等标签来轻松实现动态 SQL。然而,在 Spring Data JPA 结合 Querydsl 的场景下,实现类似的动态 SQL 逻辑则相对较为复杂。由于目前使用这种组合的人数较少,因此在网上找到相关的教程和资源也比较困难。
在 QueryDSL 中我们可以通过StreamAPI来实现类似于 mybatis 中的 <foreach> 标签。
public boolean isNotDoingTask(Map<Long, Long> instanceProcessMap) {
JPAQuery<Long> jpaQuery = jpaQueryFactory.select(qdo.id)
.from(qdo)
.where(qdo.operationType.ne("DOING")
.and(qdo.operationType.isNotNull())
.and(qdo.operationType.ne("RECALL"))
.and(instanceProcessMap.keySet().stream()
.map(key -> qdo.processId.eq(key)
.and(qdo.instanceDetailId.eq(instanceProcessMap.get(key))))
.reduce(BooleanExpression::or)
.orElse(null) // 如果有任何条件,则使用or连接,否则为null
)
);
return jpaQuery.fetch().isEmpty();
public List<PrdHomepageDocConfigVO> findAuthList(Long userId, Long orgId, List<Long> roleIds) {
JPAQuery<PrdHomepageDocConfigVO> jpaQuerySelect = getJpaQuerySelect().where(
qdo.deleteFlag.eq(0)
.and( qdo.publicScope.eq(DocConfigEnum.PublicScope.PUBLIC.getCode()))
.or(qdo.publicScope.eq(DocConfigEnum.PublicScope.APPOINT_RES.getCode())
.and(qdo.personnelObject.prepend(",").concat(",").like("%" + userId + "%")))
.or(qdo.publicScope.eq(DocConfigEnum.PublicScope.APPOINT_ORG.getCode())
.and(qdo.personnelObject.prepend(",").concat(",").like("%" + orgId + "%")))
.or(qdo.publicScope.eq(DocConfigEnum.PublicScope.APPOINT_ROLE.getCode())
.and(roleIds.stream()
.map(roleId -> qdo.personnelObject.prepend(",").concat(",").like("%" + roleId + "%"))
.reduce(BooleanExpression::or).orElse(null))));
return jpaQuerySelect.fetch();
}
3.子查询
JPAQuery<PrdDocInspectVO> jpaQuery = jpaQueryFactory.select(Projections.bean(PrdDocInspectVO.class,
qdo
))
.from(qdo)
.leftJoin(qdoProject).on(qdoProject.id.eq(qdo.projectId)
.and(qdoProject.deleteFlag.eq(0)))
.leftJoin(qdoInspectRecord)
.on(qdoInspectRecord.docId.eq(qdo.id)
.and(qdoInspectRecord.createTime.eq(
JPAExpressions.select(qdoInspectRecord.createTime.max())
.from(qdoInspectRecord)
.where(qdoInspectRecord.docId.eq(qdo.id)
.and(qdoInspectRecord.whetherRelease.eq(true))
.and(qdoInspectRecord.docType.eq(DocConfigEnum.ReasonType.OPPO.getCode()))
))))
.where(qdo.deleteFlag.eq(0));