[Spring] QueryDSL로 서브쿼리 사용-JPAExpressions
쿼리 DSL에서 서브쿼리를 사용하기 위해서는 JPAExpressions 라는 클래스를 사용하면 된다.
먼저 아래 게시글 상세조회 데이터를 불러오는 전체적인 예시를 보자.
이 유저가 해당 게시글에 대해 좋아요를 눌렀는지 아닌지에 대한 여부를 서브쿼리(EXISTS 서브쿼리)를 통해 가지고 오고 있다.
@Repository
@RequiredArgsConstructor
public class PostQueryRepository {
private final JPAQueryFactory jpaQueryFactory;
/**
* 게시글 상세 조회 + 현재 로그인 유저의 좋아요 여부까지 함께 조회하는 예시
*
* @param postId 상세 조회할 게시글 ID
* @param currentUserId 현재 로그인한 사용자 ID
* @return PostDetailResponse
*/
public PostDetailResponse findPostDetail(Long postId, Long currentUserId) {
QPost post = QPost.post;
QUser writer = QUser.user;
QPostCategory category = QPostCategory.postCategory;
QPostLike postLike = QPostLike.postLike;
return jpaQueryFactory
.select(
Projections.constructor(
PostDetailResponse.class,
writer.id, // writerId
writer.nickname, // writerNickname
category.name, // categoryName
writer.jobTitle, // writerJobTitle
post.title, // title
post.content, // content
post.thumbnailUrl, // thumbnailUrl
post.createdAt, // createdAt
post.likeCount, // likeCount
// 🔥 EXISTS 서브쿼리로 "현재 유저가 좋아요 눌렀는지" 여부 조회
JPAExpressions
.selectOne()
.from(postLike)
.where(
postLike.post.eq(post), // 같은 게시글에 대한 좋아요인지
postLike.user.id.eq(currentUserId), // 현재 로그인한 유저가 누른 좋아요인지
postLike.delYn.eq(YnFlag.N) // 논리 삭제되지 않은 좋아요만
)
.exists() // exists(...) 결과를 boolean 으로 반환
)
)
.from(post)
.join(post.writer, writer)
.join(post.category, category)
.where(
post.id.eq(postId),
post.delYn.eq(YnFlag.N)
)
.fetchOne();
}
}
JPAExpressions란?
JPAExpressions는 쿼리 DSL에서 각종 서브쿼리를 만들 때 전반적으로 쓰는 유틸 클래스다.
*기본문법
JPAExpressions
.select()
.from()
.where()
이 패턴이 들어가면 이 패턴이 사용되는 위치에 들어갈 서브쿼리를 하나 만드는 것이다.
앞서 들었던 예시는 EXISTS문에 서브쿼리를 사용했는데 EXISTS(SELECT 1~) 처럼
JPAExpressions.selectOne().~으로 시작하여 마지막에 .exists()를 붙였다.
이러면 EXISTS(서브쿼리)문이 되는 것이다.
가장 기본적인 예시로는 SELECT문안에 서브쿼리를 사용하는 것일 것이다.
게시물의 id와 해당 게시물에 달린 댓글중 가장 최근 댓글 생성시간을 가지고 오고 있다.
(*상관 서브쿼리란? 서브쿼리 안에서 바깥 쿼리를 참조하고 있는 것을 상관 서브쿼리라고 한다
아래 코드에서는 서브쿼리 안에서 comment.post.id가 바깥 쿼리의 post.id를 참조하고 있기 때문에 상관 서브쿼리다)
List<Tuple> result = jpaQueryFactory
.select(
post.id,
JPAExpressions
.select(comment.createdAt.max())
.from(comment)
.where(comment.post.eq(post)) // 상관 서브쿼리
)
.from(post)
.fetch();
//->SQL로 변환 //
SELECT
p.id,
(SELECT MAX(c.created_at)
FROM comment c
WHERE c.post_id = p.id) AS last_comment_time
FROM post p;
또 다른 예시로는 IN (서브쿼리)도 있다.
아래 예시는 내가 팔로우 하는 유저들의 글만 가져오는 코드다.
in()절 안에 JPAExpressions를 이용하여 서브쿼리를 작성했다.
List<Post> posts = jpaQueryFactory
.selectFrom(post)
.where(
post.writer.id.in(
JPAExpressions
.select(follow.following.id)
.from(follow)
.where(follow.follower.id.eq(currentUserId))
)
)
.fetch();
//->SQL문으로 바꾸면//
SELECT *
FROM post p
WHERE p.writer_id IN (
SELECT f.following_id
FROM follow f
WHERE f.follower_id = :currentUserId
)
WHERE절에 서브쿼리를 사용하는 경우다.
List<Post> hotPosts = jpaQueryFactory
.selectFrom(post)
.where(
JPAExpressions
.select(comment.count())
.from(comment)
.where(comment.post.eq(post))
.gt(10L) // > 10
)
.fetch();
//->SQL로 변환 //
SELECT *
FROM post p
WHERE (SELECT COUNT(*)
FROM comment c
WHERE c.post_id = p.id) > 10;
orderBy절에 서브쿼리를 사용하는 경우
List<Post> posts = jpaQueryFactory
.selectFrom(post)
.orderBy(
JPAExpressions
.select(comment.count())
.from(comment)
.where(comment.post.eq(post))
.desc()
)
.fetch();
이렇게
JPAExpressions를 사용해 쿼리DSL에서 서브쿼리를 만들 수 있고,
EXISTS(서브쿼리), IN(서브쿼리),ORDER BY(서브쿼리) 이런식으로 사용할 수 있다.