Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public class ReviewRepositoryImpl implements ReviewRepository {
private final ReviewJpaRepository reviewJpaRepository;
private final JPAQueryFactory queryFactory;

private final static QReview review = QReview.review;
private final static QVolunteerApply volunteerApply = QVolunteerApply.volunteerApply;
private final static QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard;
private static final QReview review = QReview.review;
private static final QVolunteerApply volunteerApply = QVolunteerApply.volunteerApply;
private static final QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard;

@Override
public Review save(Review review) {
Expand All @@ -55,31 +55,31 @@ public boolean existsByVolunteerApplyId(Long volunteerApplyId) {
public Page<Review> findAllByVolunteerIdAndSearch(UUID volunteerId,
ReviewSearchCondition condition) {

BooleanExpression predicate = review.volunteerId.eq(volunteerId)
.and(eqVolunteerCategory(condition.category()))
BooleanExpression exp = volunteerIdEq(volunteerId)
.and(volunteerCategoryEq(condition.category()))
.and(isNotDeleted());

return getReviews(condition, predicate);
return getReviews(condition, exp);

}

@Override
public Page<Review> findAllByCenterIdAndSearch(UUID centerId, ReviewSearchCondition condition) {

BooleanExpression predicate = recruitBoard.centerId.eq(centerId)
.and(eqVolunteerCategory(condition.category()))
BooleanExpression exp = centerIdEq(centerId)
.and(volunteerCategoryEq(condition.category()))
.and(isNotDeleted());

return getReviews(condition, predicate);
return getReviews(condition, exp);
}

@NotNull
private Page<Review> getReviews(ReviewSearchCondition condition, BooleanExpression predicate) {
private Page<Review> getReviews(ReviewSearchCondition condition, BooleanExpression exp) {
List<Review> content = queryFactory.select(review)
Copy link
Collaborator

@7zrv 7zrv Dec 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

조회에 사용되는 쿼리 같은데 Review의 모든 컬럼이 필요한건지 여쭤보고 싶습니다
만약 아니라면 필요한 컬럼만 추출해서 응답시간 단축, 자원 절약을 시도해보는게 어떨까요
쿼리 최적화를 위한 6가지팁

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

모두 필요합니다 리스트 조회에 대한 거라 필요합니다.

.from(review)
.join(volunteerApply).on(review.volunteerApplyId.eq(volunteerApply.id))
.join(recruitBoard).on(recruitBoard.id.eq(volunteerApply.recruitBoardId))
.where(predicate)
.where(exp)
.offset(condition.pageable().getOffset())
.limit(condition.pageable().getPageSize())
.orderBy(toOrderSpecifiers(condition.pageable().getSort()))
Expand All @@ -90,16 +90,24 @@ private Page<Review> getReviews(ReviewSearchCondition condition, BooleanExpressi
.from(review)
.join(volunteerApply).on(review.volunteerApplyId.eq(volunteerApply.id))
.join(recruitBoard).on(recruitBoard.id.eq(volunteerApply.recruitBoardId))
.where(predicate);
.where(exp);

return PageableExecutionUtils.getPage(content, condition.pageable(), countQuery::fetchOne);
}

private static BooleanExpression volunteerIdEq(UUID volunteerId) {
return review.volunteerId.eq(volunteerId);
}

private static BooleanExpression centerIdEq(UUID centerId) {
return recruitBoard.centerId.eq(centerId);
}

private BooleanExpression isNotDeleted() {
return review.deleted.isFalse();
}

private BooleanExpression eqVolunteerCategory(VolunteerCategory category) {
private BooleanExpression volunteerCategoryEq(VolunteerCategory category) {
return category != null
? recruitBoard.recruitmentInfo.volunteerCategory.eq(category) : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.somemore.domains.review.dto.request.ReviewCreateRequestDto;
import com.somemore.domains.review.usecase.CreateReviewUseCase;

import com.somemore.global.exception.DuplicateException;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -27,7 +28,7 @@ public class CreateReviewService implements CreateReviewUseCase {
@Override
public Long createReview(ReviewCreateRequestDto requestDto, UUID volunteerId, String imgUrl) {
VolunteerApply apply = getVolunteerApply(requestDto.recruitBoardId(), volunteerId);
validateReviewNotExist(apply);
validateDuplicateReview(apply);
validateActivityCompletion(apply);

Review review = requestDto.toEntity(apply, volunteerId, imgUrl);
Expand All @@ -38,16 +39,16 @@ private VolunteerApply getVolunteerApply(Long recruitBoardId, UUID volunteerId)
return volunteerApplyQueryUseCase.getByRecruitIdAndVolunteerId(recruitBoardId, volunteerId);
}

private void validateReviewNotExist(VolunteerApply apply) {
private void validateDuplicateReview(VolunteerApply apply) {
if (reviewRepository.existsByVolunteerApplyId(apply.getId())) {
throw new BadRequestException(REVIEW_ALREADY_EXISTS.getMessage());
throw new DuplicateException(REVIEW_ALREADY_EXISTS);
}
}

private void validateActivityCompletion(VolunteerApply apply) {
if (apply.isVolunteerActivityCompleted()) {
return;
}
throw new BadRequestException(REVIEW_RESTRICTED_TO_ATTENDED.getMessage());
throw new BadRequestException(REVIEW_RESTRICTED_TO_ATTENDED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import com.somemore.domains.volunteerapply.dto.response.VolunteerApplySummaryResponseDto;
import com.somemore.domains.volunteerapply.repository.VolunteerApplyRepository;
import com.somemore.domains.volunteerapply.usecase.VolunteerApplyQueryUseCase;
import com.somemore.global.exception.BadRequestException;

import com.somemore.global.exception.NoSuchElementException;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
Expand All @@ -35,7 +34,7 @@ public List<UUID> getVolunteerIdsByRecruitIds(List<Long> recruitIds) {
public VolunteerApply getByRecruitIdAndVolunteerId(Long recruitId, UUID volunteerId) {
return volunteerApplyRepository.findByRecruitIdAndVolunteerId(recruitId, volunteerId)
.orElseThrow(
() -> new BadRequestException(NOT_EXISTS_VOLUNTEER_APPLY));
() -> new NoSuchElementException(NOT_EXISTS_VOLUNTEER_APPLY));
}

@Override
Expand All @@ -49,7 +48,7 @@ public VolunteerApplySummaryResponseDto getSummaryByRecruitId(Long recruitId) {

@Override
public VolunteerApplyResponseDto getVolunteerApplyByRecruitIdAndVolunteerId(Long recruitId,
UUID volunteerId) {
UUID volunteerId) {
VolunteerApply apply = getByRecruitIdAndVolunteerId(recruitId, volunteerId);

return VolunteerApplyResponseDto.from(apply);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

public class DuplicateException extends RuntimeException{

public DuplicateException(final ExceptionMessage message) {
super(message.getMessage());
}
public DuplicateException(final String message) {
super(message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ ProblemDetail handleDuplicateException(final DuplicateException e) {
@ExceptionHandler(MethodArgumentNotValidException.class)
ProblemDetail handleMethodArgumentNotValid(final MethodArgumentNotValidException e) {

ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage());
String errorMessage = e.getBindingResult().getAllErrors().getFirst().getDefaultMessage();
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, errorMessage);

problemDetail.setTitle("유효성 예외");
problemDetail.setDetail("입력 데이터 유효성 검사가 실패했습니다. 각 필드를 확인해주세요.");

return problemDetail;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,31 @@
package com.somemore.domains.center.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.somemore.domains.center.dto.request.CenterProfileUpdateRequestDto;
import com.somemore.domains.center.usecase.command.UpdateCenterProfileUseCase;
import com.somemore.global.imageupload.usecase.ImageUploadUseCase;
import com.somemore.support.ControllerTestSupport;
import com.somemore.support.annotation.WithMockCustomUser;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.RequestPostProcessor;

import java.util.UUID;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class CenterProfileCommandApiControllerTest extends ControllerTestSupport {

@Autowired
private MockMvc mockMvc;

@Autowired
private ObjectMapper objectMapper;

@MockBean
private UpdateCenterProfileUseCase updateCenterProfileUseCase;

Expand Down Expand Up @@ -92,7 +81,6 @@ public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request)
.header("Authorization", "Bearer access-token"))

//then
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").isEmpty())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package com.somemore.domains.center.controller;

import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_CENTER;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.somemore.domains.center.dto.response.CenterProfileResponseDto;
import com.somemore.domains.center.usecase.query.CenterQueryUseCase;
import com.somemore.global.exception.BadRequestException;
import com.somemore.support.ControllerTestSupport;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;

import java.util.List;
import java.util.UUID;

import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_CENTER;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class CenterQueryApiControllerTest extends ControllerTestSupport {

@MockBean
Expand Down Expand Up @@ -53,16 +53,18 @@ void getCenterProfile() throws Exception {
get("/api/center/profile/{centerId}", centerId)
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value("200"))
.andExpect(jsonPath("$.message").value("기관 프로필 조회 성공"))
.andExpect(jsonPath("$.data.center_id").value(centerId.toString())) // center_id로 수정
.andExpect(jsonPath("$.data.name").value("Test Center"))
.andExpect(jsonPath("$.data.contact_number").value("010-1234-5678")) // contact_number로 수정
.andExpect(jsonPath("$.data.img_url").value("http://example.com/image.jpg")) // img_url로 수정
.andExpect(jsonPath("$.data.contact_number").value(
"010-1234-5678")) // contact_number로 수정
.andExpect(jsonPath("$.data.img_url").value(
"http://example.com/image.jpg")) // img_url로 수정
.andExpect(jsonPath("$.data.introduce").value("This is a test center."))
.andExpect(jsonPath("$.data.homepage_link").value("http://example.com")) // homepage_link로 수정
.andExpect(jsonPath("$.data.homepage_link").value(
"http://example.com")) // homepage_link로 수정
.andExpect(jsonPath("$.data.prefer_items").isArray()); // prefer_items로 수정
Comment on lines 59 to 68
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석의미를 여쭤보고 싶습니다~

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 기존 코드라 제가 작성한 부분이 아니에요


verify(centerQueryUseCase, times(1)).getCenterProfileByCenterId(centerId);
Expand All @@ -81,7 +83,6 @@ void getCenterProfile_NotFound() throws Exception {
get("/api/center/profile/{centerId}", nonExistentCenterId)
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value("400"))
.andExpect(jsonPath("$.detail").value("존재하지 않는 기관입니다."));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
package com.somemore.domains.center.controller;

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.somemore.domains.center.dto.request.PreferItemCreateRequestDto;
import com.somemore.domains.center.dto.response.PreferItemCreateResponseDto;
import com.somemore.domains.center.usecase.command.CreatePreferItemUseCase;
import com.somemore.domains.center.usecase.command.DeletePreferItemUseCase;
import com.somemore.global.exception.BadRequestException;
import com.somemore.support.ControllerTestSupport;
import com.somemore.support.annotation.WithMockCustomUser;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;

import java.util.UUID;

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class PreferItemCommandApiControllerTest extends ControllerTestSupport {

@MockBean
Expand Down
Loading
Loading