Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,34 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// QueryDSL : OpenFeign
implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0"
implementation "io.github.openfeign.querydsl:querydsl-core:7.0"
annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.0:jpa"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
}

tasks.named('test') {
useJUnitPlatform()
}

// QueryDSL 관련 설정
// generated/querydsl 폴더 생성 & 삽입
def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile

// 소스 세트에 생성 경로 추가 (구체적인 경로 지정)
sourceSets {
main.java.srcDirs += [ querydslDir ]
}

// 컴파일 시 생성 경로 지정
tasks.withType(JavaCompile).configureEach {
options.generatedSourceOutputDirectory.set(querydslDir)
}

// clean 태스크에 생성 폴더 삭제 로직 추가
clean.doLast {
file(querydslDir).deleteDir()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.example.umc9th.domain.review.controller;

import com.example.umc9th.domain.review.dto.ReviewMyReviewResponse;
import com.example.umc9th.domain.review.service.ReviewService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class ReviewController {

private final ReviewService reviewService;

@GetMapping("/users/{userId}/reviews")
public ResponseEntity<Page<ReviewMyReviewResponse>> getMyReviews(@PathVariable Long userId,
@RequestParam(required = false) String restaurantName,
@RequestParam(required = false) Integer ratingFloor,
@PageableDefault(size = 10) Pageable pageable) {
Page<ReviewMyReviewResponse> response = reviewService.getMyReviews(userId, restaurantName, ratingFloor, pageable);
return ResponseEntity.ok(response);
}
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.umc9th.domain.review.converter;

import com.example.umc9th.domain.review.dto.ReviewMyReviewResponse;
import com.example.umc9th.domain.review.repository.result.ReviewSummaryProjection;

public final class ReviewConverter {

private ReviewConverter() {
}

public static ReviewMyReviewResponse toMyReviewResponse(ReviewSummaryProjection projection) {
return new ReviewMyReviewResponse(
projection.reviewId(),
projection.restaurantName(),
projection.reviewStar(),
projection.body(),
projection.createdAt()
);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.umc9th.domain.review.dto;

import java.time.LocalDateTime;

public record ReviewMyReviewResponse(
Long reviewId,
String restaurantName,
Integer reviewStar,
String body,
LocalDateTime createdAt
) {
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.example.umc9th.domain.review.enums;

import java.util.Arrays;

public enum ReviewRatingGroup {

FIVE(5, 5),
FOUR(4, 4),
THREE(3, 3),
TWO(2, 2),
ONE(1, 1);

private final int minInclusive;
private final int maxInclusive;

ReviewRatingGroup(int minInclusive, int maxInclusive) {
this.minInclusive = minInclusive;
this.maxInclusive = maxInclusive;
}

public int getMinInclusive() {
return minInclusive;
}

public int getMaxInclusive() {
return maxInclusive;
}

public static ReviewRatingGroup fromValue(Integer value) {
if (value == null) {
return null;
}
return Arrays.stream(values())
.filter(group -> group.minInclusive == value)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unsupported rating group value: " + value));
}
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.umc9th.domain.review.repository;

import com.example.umc9th.domain.review.enums.ReviewRatingGroup;
import com.example.umc9th.domain.review.repository.result.ReviewSummaryProjection;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface ReviewQueryRepository {

Page<ReviewSummaryProjection> findMyReviews(Long userId,
String restaurantName,
ReviewRatingGroup ratingGroup,
Pageable pageable);
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.example.umc9th.domain.review.repository;

import com.example.umc9th.domain.restaurant.entity.QRestaurant;
import com.example.umc9th.domain.review.entity.QReview;
import com.example.umc9th.domain.review.enums.ReviewRatingGroup;
import com.example.umc9th.domain.review.repository.result.ReviewSummaryProjection;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import java.util.List;

@Repository
@RequiredArgsConstructor
public class ReviewQueryRepositoryImpl implements ReviewQueryRepository {

private final JPAQueryFactory queryFactory;

private static final QReview review = QReview.review;
private static final QRestaurant restaurant = QRestaurant.restaurant;

@Override
public Page<ReviewSummaryProjection> findMyReviews(Long userId,
String restaurantName,
ReviewRatingGroup ratingGroup,
Pageable pageable) {
BooleanBuilder builder = new BooleanBuilder()
.and(review.user.id.eq(userId));

if (StringUtils.hasText(restaurantName)) {
builder.and(restaurant.name.eq(restaurantName));
}

BooleanExpression ratingCondition = ratingGroupCondition(ratingGroup);
if (ratingCondition != null) {
builder.and(ratingCondition);
}

List<ReviewSummaryProjection> content = queryFactory
.select(Projections.constructor(ReviewSummaryProjection.class,
review.id,
restaurant.name,
review.reviewStar,
review.body,
review.createdAt
))
.from(review)
.join(review.restaurant, restaurant)
.where(builder)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(review.createdAt.desc())
.fetch();

Long total = queryFactory
.select(review.count())
.from(review)
.join(review.restaurant, restaurant)
.where(builder)
.fetchOne();

long totalElements = total != null ? total : 0L;
return new PageImpl<>(content, pageable, totalElements);
}

private BooleanExpression ratingGroupCondition(ReviewRatingGroup ratingGroup) {
if (ratingGroup == null) {
return null;
}
return review.reviewStar.between(ratingGroup.getMinInclusive(), ratingGroup.getMaxInclusive());
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import com.example.umc9th.domain.review.entity.Review;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ReviewRepository extends JpaRepository<Review, Long> {
public interface ReviewRepository extends JpaRepository<Review, Long>, ReviewQueryRepository {
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.umc9th.domain.review.repository.result;

import java.time.LocalDateTime;

public record ReviewSummaryProjection(
Long reviewId,
String restaurantName,
Integer reviewStar,
String body,
LocalDateTime createdAt
) {
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.umc9th.domain.review.service;

import com.example.umc9th.domain.review.converter.ReviewConverter;
import com.example.umc9th.domain.review.dto.ReviewMyReviewResponse;
import com.example.umc9th.domain.review.enums.ReviewRatingGroup;
import com.example.umc9th.domain.review.repository.ReviewRepository;
import com.example.umc9th.domain.review.repository.result.ReviewSummaryProjection;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ReviewService {

private final ReviewRepository reviewRepository;

public Page<ReviewMyReviewResponse> getMyReviews(Long userId,
String restaurantName,
Integer ratingFloor,
Pageable pageable) {
ReviewRatingGroup ratingGroup = ReviewRatingGroup.fromValue(ratingFloor);

Page<ReviewSummaryProjection> projectionPage = reviewRepository.findMyReviews(
userId,
restaurantName,
ratingGroup,
pageable
);

return projectionPage.map(ReviewConverter::toMyReviewResponse);
}
}



22 changes: 22 additions & 0 deletions src/main/java/com/example/umc9th/global/config/QuerydslConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.umc9th.global.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuerydslConfig {

@PersistenceContext
private EntityManager entityManager;

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}