背景
本文是 Spring Data JPA 多条件连表查询 文章的最佳实践总结。
解决什么问题?
使用 Spring Data JPA 需要针对多条件进行连表查询的场景不使用原生 SQL 或者 HQL 的时候,仅仅通过 JpaSpecificationExecutor 构造 Specification 动态条件语句来实现类型安全的多条件查询。
说明
相关上下文背景请前往 前文 了解。 这里再提一下接下来示例会用到的场景:
三个实体:作者、书、书评。其中,作者与书是一对多的关系,书与书评是一对一的关系(当然书评与读者的评价是一对多的关系,这里省去,仅用一对一来进行演示即可)。
假设有这样的后台查询条件:作者名称、书的发布时间、书评的评分。(这里每个实体取一个字段进行连表查询演示,其他字段同理)。返回书籍列表以及相关表字段。
【本文所有代码在此】
最佳实践
- 需要
SELECT
查询的字段,通过单独的 Java Bean 进行映射- 利用 JPA 的自动实体映射结果集
@EntityGraph
注解标注返回实体需要 Fetch 的字段- 无需再手动针对连表进行
fetch
,解决 N+1 问题
- 无需再手动针对连表进行
- JOIN ON 查询条件使用
join().on()
拼接Join<Object, Object> author = root.join("author"); author.on(cb.equal(author.get("name"), param.getAuthorName()));
- WHERE 查询条件使用
query.where()
拼接query.where(cb.equal(root.get("publishTime"), param.getBookPublishTime()));
代码示例
针对 Repository
- 需要
override
已有findAll
方法,使用@EntityGraph
注解 - 使用
@EntityGraph
注解,标注额外属性需要 fetch
// BookJoinRepository.java
@Repository
public interface BookJoinRepository extends
JpaRepository<BookJoin, String>, JpaSpecificationExecutor<BookJoin> {
@Override
@EntityGraph(attributePaths = { "author", "review" })
Page<BookJoin> findAll(Specification<BookJoin> spec, Pageable pageable);
}
针对 Specification
- WHERE 查询条件使用
query.where()
拼接 - JOIN ON 查询条件使用
join().on()
拼接
static Specification<BookJoin> multiQuery_04(BookJoinQuery param) {
return (root, query, cb) -> {
if (null != param.getBookPublishTime()) {
query.where(cb.equal(root.get("publishTime"), param.getBookPublishTime()));
}
if (null != param.getAuthorName()) {
Join<Object, Object> author = root.join("author");
author.on(cb.equal(author.get("name"), param.getAuthorName()));
}
if (null != param.getReviewScore()) {
Join<Object, Object> review = root.join("review");
review.on(cb.equal(review.get("score"), param.getReviewScore()));
}
return query.getRestriction();
};
}
结果 SQL 语句
select bookjoin0_.id as id1_1_0_,
bookjoin_a1_.id as id1_0_1_,
bookjoin_r2_.id as id1_2_2_,
bookjoin0_.author_id as author_i3_1_0_,
bookjoin0_.publish_time as publish_2_1_0_,
bookjoin0_.review_id as review_i4_1_0_,
bookjoin_a1_.name as name2_0_1_,
bookjoin_r2_.score as score2_2_2_
from book bookjoin0_
inner join author bookjoin_a1_ on bookjoin0_.author_id = bookjoin_a1_.id
and (bookjoin_a1_.name = ?)
inner join review bookjoin_r2_ on bookjoin0_.review_id = bookjoin_r2_.id
and (bookjoin_r2_.score = ?)
where bookjoin0_.publish_time = ? limit ?
当然,这里案例使用的是 INNER JOIN
,对于 LEFT JOIN
也是生效的。
Join<Object, Object> author = root.join("author", JoinType.LEFT);
author.on(cb.equal(author.get("name"), param.getAuthorName()));
// left outer join author bookjoin_a1_ on
// bookjoin0_.author_id = bookjoin_a1_.id
// and (bookjoin_a1_.name = ?)