Skip to content

Commit

Permalink
Merge pull request #245 from linglong67/feature/setting-querydsl
Browse files Browse the repository at this point in the history
[feat] Querydsl 세팅 및 적용
  • Loading branch information
linglong67 authored Mar 4, 2024
2 parents 878d819 + ff44b50 commit 89c459c
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 18 deletions.
6 changes: 6 additions & 0 deletions module-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ dependencies {

//mybatis
implementation group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '3.0.3'

// querydsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ public class ReviewController {

private final ReviewService reviewService;

@GetMapping("/product/{productNo}")
public ResponseEntity<ApiResponse<Page<ReviewDto>>> getReviewsByProduct(@PathVariable Long productNo, Pageable pageable) {
@GetMapping("")
public ResponseEntity<ApiResponse<Page<ReviewDto>>> getReviewsByProduct(
@RequestParam(name = "productNo") Long productNo,
@RequestParam(name = "sortBy", defaultValue = "reviewNo", required = false) String sortBy,
Pageable pageable) {

return ApiResponse.toResponseEntity(ReviewBusinessCode.SUCCESS_GET_REVIEWS, reviewService.getReviewsByProduct(productNo, pageable));
return ApiResponse.toResponseEntity(ReviewBusinessCode.SUCCESS_GET_REVIEWS, reviewService.getReviewsByProduct(productNo, sortBy, pageable));
}

@GetMapping("/{reviewNo}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.kernel360.review.dto;

public record ReviewSearchDto(
Long productNo,
Long memberNo,
String sortBy
) {
public static ReviewSearchDto of(Long productNo, Long memberNo, String sortBy) {
return new ReviewSearchDto(productNo, memberNo, sortBy);
}

public static ReviewSearchDto byProductNo(Long productNo, String sortBy) {
return ReviewSearchDto.of(productNo, null, sortBy);
}

// TODO: 추후 mypage 리뷰 관리에서 사용 예정
public static ReviewSearchDto byMemberNo(Long memberNo, String sortBy) {
return ReviewSearchDto.of(null, memberNo, sortBy);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.kernel360.review.repository;

public interface ReviewRepository extends ReviewRepositoryJpa, ReviewRepositoryDsl {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.kernel360.review.repository;

import com.kernel360.review.dto.ReviewSearchDto;
import com.kernel360.review.entity.Review;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface ReviewRepositoryDsl {
Page<Review> findAllByCondition(ReviewSearchDto condition, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.kernel360.review.repository;

import com.kernel360.review.dto.ReviewSearchDto;
import com.kernel360.review.entity.Review;
import com.querydsl.core.types.OrderSpecifier;
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 java.util.List;

import static com.kernel360.review.entity.QReview.review;

@RequiredArgsConstructor
public class ReviewRepositoryImpl implements ReviewRepositoryDsl {

private final JPAQueryFactory queryFactory;

@Override
public Page<Review> findAllByCondition(ReviewSearchDto condition, Pageable pageable) {
List<Review> reviews = queryFactory
.select(review)
.from(review)
.where(
productNoEq(condition.productNo()),
memberNoEq(condition.memberNo()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(sort(condition.sortBy()))
.fetch();

Long totalCount = queryFactory
.select(review.count())
.from(review)
.where(
productNoEq(condition.productNo()),
memberNoEq(condition.memberNo()))
.fetchOne();

return new PageImpl<>(reviews, pageable, totalCount);
}

private BooleanExpression productNoEq(Long productNo) {
return productNo == null ? null : review.product.productNo.eq(productNo);
}

private BooleanExpression memberNoEq(Long memberNo) {
return memberNo == null ? null : review.member.memberNo.eq(memberNo);
}

private static OrderSpecifier<? extends Number> sort(String sortBy) {
if ("topRated".equals(sortBy)) {
return review.starRating.desc();
}

if ("lowRated".equals(sortBy)) {
return review.starRating.asc();
}

return review.reviewNo.desc();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.kernel360.exception.BusinessException;
import com.kernel360.review.code.ReviewErrorCode;
import com.kernel360.review.dto.ReviewDto;
import com.kernel360.review.dto.ReviewSearchDto;
import com.kernel360.review.entity.Review;
import com.kernel360.review.repository.ReviewRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,10 +26,11 @@ public class ReviewService {
private static final double MAX_STAR_RATING = 5.0;

@Transactional(readOnly = true)
public Page<ReviewDto> getReviewsByProduct(Long productNo, Pageable pageable) {
public Page<ReviewDto> getReviewsByProduct(Long productNo, String sortBy, Pageable pageable) {
log.info("제품 리뷰 목록 조회 -> product_no {}", productNo);
// TODO: 유효하지 않은 productNo 인 경우, custom error 보내기

return reviewRepository.findAllByProduct_ProductNoOrderByReviewNoDesc(productNo, pageable)
return reviewRepository.findAllByCondition(ReviewSearchDto.byProductNo(productNo, sortBy), pageable)
.map(ReviewDto::from);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public enum CommonErrorCode implements ErrorCode {
NOT_FOUND_RESOURCE(HttpStatus.NOT_FOUND.value(), "E004", "요청한 자원이 존재하지 않음"),
INVALID_REQUEST_HEADERS(HttpStatus.BAD_REQUEST.value(), "E005", "요청한 헤더가 존재하지 않음"),
INVALID_ARGUMENT(HttpStatus.BAD_REQUEST.value(), "E006", "요청 파라미터가 없거나 비어있거나, 요청 파라미터의 이름이 메서드 인수의 이름과 일치하지 않습니다"),
INVALID_HTTP_REQUEST_METHOD(HttpStatus.BAD_REQUEST.value(), "E007", "요청 URL 에서 지원하지 않는 HTTP Method 입니다.");
INVALID_HTTP_REQUEST_METHOD(HttpStatus.BAD_REQUEST.value(), "E007", "요청 URL 에서 지원하지 않는 HTTP Method 입니다."),
INVALID_REQUEST_PARAMETER(HttpStatus.BAD_REQUEST.value(), "E008", "요청한 파라미터가 존재하지 않음");

private final int status;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

Expand Down Expand Up @@ -91,4 +92,12 @@ protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedExcep
return new ResponseEntity<>(response,HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(MissingServletRequestParameterException.class)
protected ResponseEntity<ErrorResponse> handleMissingParameterException(final MissingServletRequestParameterException e) {
log.error("handleMissingParameterException", e);

final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_REQUEST_PARAMETER);

return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
}
18 changes: 18 additions & 0 deletions module-domain/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ dependencies {
implementation group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '3.0.3'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// querydsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
}

tasks.named('test') {
Expand All @@ -43,5 +49,17 @@ tasks.jar {

tasks.register("prepareKotlinBuildScriptModel") {}

def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile;

tasks.withType(JavaCompile) {
options.getGeneratedSourceOutputDirectory().set(file(querydslDir))
}

sourceSets {
main.java.srcDirs += [ querydslDir ]
}

clean {
delete file(querydslDir)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.kernel360.config;

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

@Configuration
public class QuerydslConfig {

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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.kernel360.review.repository;

import com.kernel360.review.entity.Review;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ReviewRepositoryJpa extends JpaRepository<Review, Long> {
Review findByReviewNo(Long reviewNo);
}

0 comments on commit 89c459c

Please sign in to comment.