스프링부트/JPA

[Spring] QueryDSL로 서브쿼리 사용-JPAExpressions

삼록이 2025. 11. 25. 09:58

쿼리 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(서브쿼리) 이런식으로 사용할 수 있다.