본문 바로가기
개발/Spring

[리팩토링] Spring-data-JPA와 Querydsl을 함께 사용하며

by meanjung 2023. 10. 1.

기존 코드

no offset paging을 구현하면서 querydsl을 사용하기 시작했다. 

 

하지만 spring-data-jpa와 함께 사용하며 기존 코드는 다음과 같았다. 

 

public interface ReviewRepository extends JpaRepository<Review, Long> {

    Review findByReviewId(Long reviewId);
    List<Review> findAllByUser(User user);

}

JpaRepository를 상속하는 ReviewRepository 인터페이스를 두고

 

querydsl을 사용하는 쿼리는 Service 단에 위치해 있었다. 

 

@Slf4j
@Service
@RequiredArgsConstructor
public class ReviewService {

    private final ReviewRepository reviewRepository;

    private final JPAQueryFactory queryFactory;


    @Transactional(readOnly = true)
    public List<ReviewInBookDetailResponse> getBookDetailReviewNoOffset(Long bookId, Long reviewId){
        BooleanBuilder dynamicLtId = new BooleanBuilder();

        if (reviewId != null) {
            dynamicLtId.and(review.reviewId.lt(reviewId));
        }

        return queryFactory.select(Projections.constructor(ReviewInBookDetailResponse.class,
                        review.reviewTitle, review.reviewContent, review.reviewId, review.user.userNickname.as("reviewerNickname")))
                .from(review)
                .innerJoin(review.user, user)
                .where(dynamicLtId
                        .and(review.book.bookId.eq(bookId)))
                .orderBy(review.reviewId.desc())
                .limit(3)
                .fetch();
    }


    @Transactional(readOnly = true)
    public ReviewDetailResponse getReviewDetail(Long reviewId){
        ReviewDetailResponse response =queryFactory
                .select(new QReviewDetailResponse(
                            review.reviewTitle,
                            review.reviewContent,
                            review.reviewImg,
                            user.userNickname,
                            user.userPhoto
                    ))
                .from(review)
                .innerJoin(review.user, user)
                .where(review.reviewId.eq(reviewId))
                .fetchOne();
        List<ReviewDetailCommentResponse> comments = queryFactory
                .select(new QReviewDetailCommentResponse(
                        user.userId,
                        user.userNickname,
                        user.userPhoto,
                        reviewComment.commentText
                ))
                .from(reviewComment)
                .innerJoin(reviewComment.user, user)
                .where(reviewComment.review.reviewId.eq(reviewId))
                .fetch();

        response.setReviewComments(comments);
        return response;

    }

/*
 * 그 외 ReviewRepository를 사용하는 메서드들
 */


}

 

 

하지만 역할의 분리가 제대로 되어있지 않다고 생각했다. 

그래서 Spring-data-jpa와 querydsl을 함께 사용할 때 querydsl의 Repository를 작성하는 방법에 대해 찾아보았고, 해결책을 발견했다. 

 

 

개선한 코드

public interface ReviewRepositoryCustom {
    List<ReviewInBookDetailResponse> findReviewByBookIdNoOffset(Long bookId, Long reviewId);
    ReviewDetailResponse findReviewById(Long reviewId);
    List<ReviewDetailCommentResponse> findReviewCommentById(Long reviewId);    
}

 

@Repository
@RequiredArgsConstructor
public class ReviewRepositoryImpl implements ReviewRepositoryCustom{

    private final JPAQueryFactory queryFactory;

    @Override
    public List<ReviewInBookDetailResponse> findReviewByBookIdNoOffset(Long bookId, Long reviewId) {

        BooleanBuilder dynamicLtId = new BooleanBuilder();

        if (reviewId != null) {
            dynamicLtId.and(review.reviewId.lt(reviewId));
        }

        return queryFactory.select(
                        Projections.constructor(ReviewInBookDetailResponse.class,
                                review.reviewTitle,
                                review.reviewContent,
                                review.reviewId,
                                review.user.userNickname.as("reviewerNickname"),
                                review.user.userPhoto.as("reviewerImg")
                        ))
                .from(review)
                .innerJoin(review.user, user)
                .where(dynamicLtId
                        .and(review.book.bookId.eq(bookId)))
                .orderBy(review.reviewId.desc())
                .limit(3)
                .fetch();
    }

    @Override
    public ReviewDetailResponse findReviewById(Long reviewId) {
        return queryFactory
                .select(
                        Projections.constructor(ReviewDetailResponse.class,
                                review.reviewTitle,
                                review.reviewContent,
                                review.reviewImg,
                                user.userNickname,
                                user.userPhoto))
                .from(review)
                .innerJoin(review.user, user)
                .where(review.reviewId.eq(reviewId))
                .fetchOne();
    }

    @Override
    public List<ReviewDetailCommentResponse> findReviewCommentById(Long reviewId) {
        return queryFactory
                .select(
                        Projections.constructor(ReviewDetailCommentResponse.class,
                                user.userId,
                                user.userNickname,
                                user.userPhoto,
                                reviewComment.commentText
                        ))
                .from(reviewComment)
                .innerJoin(reviewComment.user, user)
                .where(reviewComment.review.reviewId.eq(reviewId))
                .fetch();
    }

}

 

public interface ReviewRepository extends JpaRepository<Review, Long>, ReviewRepositoryCustom {

    Review findByReviewId(Long reviewId);
    List<Review> findAllByUser(User user);

}

 

 

Service에서는 ReviewRepository 하나만으로 바로 사용할 수 있다. 

다형성의 이점을 경험한 순간이다. 

@Slf4j
@Service
@RequiredArgsConstructor
public class ReviewService {

    private final ReviewRepository reviewRepository;
    

    @Transactional(readOnly = true)
    public List<ReviewInBookDetailResponse> getBookDetailReviewNoOffset(Long bookId, Long reviewId){

        List<ReviewInBookDetailResponse> bookDetailResponseList = reviewRepository.findReviewByBookIdNoOffset(bookId, reviewId);
        return bookDetailResponseList;
    }


    @Transactional(readOnly = true)
    public ReviewDetailResponse getReviewDetail(Long reviewId){
        ReviewDetailResponse response = reviewRepository.findReviewById(reviewId);
        List<ReviewDetailCommentResponse> comments = reviewRepository.findReviewCommentById(reviewId);

        response.setReviewComments(comments);
        return response;
    }

}

 

댓글