#1. Event
#2. Feature
@Repository
public interface ReviewRepository extends JpaRepository<Review, Long>, ReviewRepositoryCustom {
// 내용 생략 ...
}
@RequiredArgsConstructor
public class ReviewRepositoryImpl implements ReviewRepositoryCustom {
private final JPAQueryFactory queryFactory;
// 내용 생략 ...
}
상세한 구현을 위해 Custom, Impl 클래스를 생성했고 필요한 상속 관계를 추가했다.
Impl은 QueryDSL로 구현하기 때문에 JPAQueryFactory가 필요하다.
public enum RatingSortType {
DATE,
RATING_ASC,
RATING_DESC,
RATING_AVG_ASC,
RATING_AVG_DESC;
}
정렬 타입을 enum으로 다음과 같이 분리했다.
import com.querydsl.core.annotations.QueryProjection;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class RatingFindAllResponse {
private String isbn;
private String title;
private String image;
private Double rating;
@Builder
@QueryProjection
public RatingFindAllResponse(String isbn, String title, String image, Double rating) {
this.isbn = isbn;
this.title = title;
this.image = image;
this.rating = rating;
}
}
응답해야 할 DTO는 다음과 같다.
현재 화면에서 필요한 isbn, title, image, rating 값만 필요하다.
이후에 더 필요한 내용은 isbn을 pk로 조회 쿼리를 날려서 찾을 수 있다.
import com.jisungin.application.review.response.RatingFindAllResponse;
import com.jisungin.domain.review.RatingSortType;
import java.util.List;
public interface ReviewRepositoryCustom {
List<RatingFindAllResponse> findAllRatingOrderBy(Long userId, RatingSortType ratingSortType);
}
import com.jisungin.application.review.response.QRatingFindAllResponse;
import com.jisungin.application.review.response.RatingFindAllResponse;
import com.jisungin.domain.review.RatingSortType;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import static com.jisungin.domain.book.QBook.book;
import static com.jisungin.domain.review.QReview.review;
import static com.jisungin.domain.review.RatingSortType.*;
@Slf4j
@RequiredArgsConstructor
public class ReviewRepositoryImpl implements ReviewRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public List<RatingFindAllResponse> findAllRatingOrderBy(Long userId, RatingSortType ratingSortType) {
log.info("-------------------------------------------");
return queryFactory.select(new QRatingFindAllResponse(
book.isbn, book.title, book.imageUrl, review.rating))
.from(review)
.leftJoin(book).on(review.book.eq(book))
.where(review.user.id.eq(userId))
.orderBy(createSpecifier(ratingSortType))
.fetch();
}
private OrderSpecifier createSpecifier(RatingSortType ratingSortType) {
if (ratingSortType.equals(RATING_ASC)) {
return review.rating.asc();
}
if (ratingSortType.equals(RATING_DESC)) {
return review.rating.desc();
}
if (ratingSortType.equals(RATING_AVG_ASC)) {
return review.rating.avg().asc();
}
if (ratingSortType.equals(RATING_AVG_DESC)) {
return review.rating.avg().desc();
}
return review.createDateTime.desc();
}
}
리뷰와 책을 조인하고 가져온 데이터를 입력된 열거 타입으로 정렬했다.
해당 함수는 new OrderSpecifier(Order.ASC, review.rating)와 같은 인자 값을 넘기면 된다.
#3. Test
class ReviewRepositoryTest extends RepositoryTestSupport {
@Autowired
private ReviewRepository reviewRepository;
@Autowired
private BookRepository bookRepository;
@Autowired
private UserRepository userRepository;
@DisplayName("리뷰 별점이 낮은 순으로 책을 정렬한다.")
@Test
void getRatingsOrderByRatingASC() {
//given
Book book1 = createBook("1");
Book book2 = createBook("2");
Book book3 = createBook("3");
bookRepository.saveAll(List.of(book1, book2, book3));
User user = createUser("1");
userRepository.save(user);
Review review1 = createReview(user, book1, 4.0);
Review review2 = createReview(user, book2, 3.0);
Review review3 = createReview(user, book3, 2.0);
reviewRepository.saveAll(List.of(review1, review2, review3));
//when
List<RatingFindAllResponse> result = reviewRepository.findAllRatingOrderBy(
user.getId(), RatingSortType.RATING_ASC);
//then
Assertions.assertThat(result)
.extracting("isbn", "title", "image", "rating")
.containsExactly(
tuple("3", "제목", "image", 2.0),
tuple("2", "제목", "image", 3.0),
tuple("1", "제목", "image", 4.0)
);
}
private static Review createReview(User user, Book book, Double rating) {
return Review.builder()
.user(user)
.book(book)
.content("내용")
.rating(rating)
.build();
}
private static User createUser(String oauthId) {
return User.builder()
.name("김도형")
.profileImage("image")
.oauthId(
OauthId.builder()
.oauthId(oauthId)
.oauthType(OauthType.KAKAO)
.build()
)
.build();
}
private static Book createBook(String isbn) {
return Book.builder()
.title("제목")
.content("내용")
.authors("김도형")
.isbn(isbn)
.publisher("지성인")
.dateTime(LocalDateTime.of(2024, 1, 1, 0, 0))
.imageUrl("image")
.build();
}
}
테스트는 3개의 리뷰 데이터를 생성하고, 정렬 타입을 실행해서 결과가 올바른지 확인다.
'Spring' 카테고리의 다른 글
STOMP로 소켓 방식 채팅 구현 + Rate Limiter, Token Bucket으로 API 처리율 제한하기 (0) | 2024.06.21 |
---|---|
Redis Sorted Sets으로 인기 검색어 구현하기 (0) | 2024.04.09 |
리뷰 좋아요 조회 쿼리 N + 1 문제 해결하기 (0) | 2024.04.05 |
[Spring] could not initialize proxy [...] - no Session 오류 (0) | 2024.04.02 |
Spring Security 없이 소셜 로그인 구현하기 (0) | 2024.03.17 |