기존 코드
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;
}
}
'개발 > Spring' 카테고리의 다른 글
[N+1 문제] EAGER로딩에서 N+1이 발생하지 않는 경우 (0) | 2023.10.03 |
---|---|
MySQL 외래키와 데드락 (1) | 2023.08.17 |
MySQL 비관적 락을 이용한 인원 제한 구현하기 (0) | 2023.08.17 |
[N+1 문제] 일반조인, fetch조인으로 OSIV, LAZY/EAGER, N+1문제 확실히 이해하기 (0) | 2023.08.16 |
JPA 프록시 개념/존재이유와 find(), getReferenceById() (0) | 2023.08.08 |
댓글