Skip to content

Commit

Permalink
[re�factor#148] 답변 채택 API 성능 최적화 (#155)
Browse files Browse the repository at this point in the history
* [test] : 채택 시 크레딧 정합성 테스트

* [feat] : QuestionPostSimpleQueryRepo 생성

- fetch join을 활용한 한방 쿼리 추가

* [feat] : AnswerSimpleQueryRepo 생성

- fetch join을 활용한 한방 쿼리 추가

* [feat] : 답변채택 쿼리 개수 개선 v2 추가

* [feat] : 답변채택 쿼리 개수 개선 v2 API 추가

* [test] : 답변채택 쿼리 개수 개선 v2 단위테스트 추가

* [test] : 답변채택 쿼리 개수 개선 v2 통합테스트 추가

* [�Refactor #151] JWT Subject 개인정보 제거 (#154)

* [refactor] JWT 생성 로직 변경

- JWT 생성시 subject에 개인정보(이메일)이 아닌 PK값이 들어가도록 변경
- 검증 토큰을 이용한 인증 객체 생성 시 subject 이메일 -> PK 변경에 따라 PK로 회원 찾도록 변경

* [refactor] : 토큰 generate 메서드 파라미터 변경으로 인한 리팩토링

* [refactor] : 토큰 generate 메서드 파라미터 변경으로 인한 리팩토링

* [refactor] : 토큰 generate 메서드 파라미터 변경으로 인한 리팩토링

* [refactor #156] 채팅 요청 목록 API에서 채팅 파트너 조회 로직 리팩토링 (#157)

* [feat] : 채팅 요청에 createdAt 필드 추가

* [feat] : 채팅 요청 테스트 픽스쳐에 createdAt 추가

* [feat] : 채팅 파트너 구하는 로직 DTO에 추가

* [refactor] : 채팅 파트너 구하는 로직 DTO로 이동

* [style] : 코드 리포멧팅

* [test] : createdAt 포함된 testFixture 사용

* [refactor] : V1, V2 통합

* [test] : V1, V2 통합에 따른 테스트 코드 삭제

* [feat #158] 채팅 요청 상세 조회 API (#159)

* [feat] : 채팅 요청 상세 조회 응답 추가

* [feat] : 채팅 요청 상세 조회 비즈니스 로직 추가

* [feat] : 채팅 요청 상세 조회 비즈니스 로직 테스트

* [feat] : 채팅 요청 상세 조회 API 메서드 추가

* [test] : 채팅 요청 상세 조회 API 메서드 테스트

* [feat] : 채팅 요청 API pk 필드명 수정

* [rename] : memberInfo DTO 위치 이동

* [remove] : 불필요한 예외 로직 처리 삭제

* [refactor] : 채팅 파트너 구하는 로직 도메인으로 이동

* [refactor] : 채팅 파트너 구하는 로직 mapper가 아닌 서비스 내에서 호출

* [style] : 코드 리포멧팅

* [style] : 줄바꿈 취소

* [test] : V1, V2 통합에 따른 테스트 코드 수정

* [feat] : QuestionPost 단건조회 메서드 추가

* [test]: 정합성 테스트 disabled 처리

* [refactor]: fetch join 함수 repository로 이동

---------

Co-authored-by: Son Gahyun <[email protected]>
Co-authored-by: hs12 <[email protected]>
  • Loading branch information
3 people authored Nov 23, 2024
1 parent 26bfa04 commit 1dd4523
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public ResponseEntity<PageResponse<AnswerDetailResponse>> getAnswersByQuestionPo
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/api/question-posts/answers/{answerId}")
public ResponseEntity<AnswerDetailResponse> getAnswersByQuestionPostId(
@PathVariable Long answerId,
@PathVariable("answerId") Long answerId,
@AuthenticationPrincipal Member member
) {
AnswerDetailResponse response = answerService.chooseAnswer(answerId, member);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dnd.gongmuin.answer.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -21,4 +22,8 @@ public interface AnswerRepository extends JpaRepository<Answer, Long> {
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Query("UPDATE Answer a SET a.member = :member WHERE a.member.id = :memberId")
void updateAnswersMember(Long memberId, Member member);

@Query("select a from Answer a "
+ "join fetch a.member where a.id = :answerId")
Optional<Answer> findByIdWithMember(Long answerId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.dnd.gongmuin.answer.repository;

import java.util.Optional;

import org.springframework.stereotype.Repository;

import com.dnd.gongmuin.answer.domain.Answer;

import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class AnswerSimpleQueryRepository {

private final EntityManager em;

public Optional<Answer> findAnswerById(Long answerId) {
Answer answer = em.createQuery(
"select a from Answer a" +
" join fetch a.member m" +
" where a.id = :answerId", Answer.class
)
.setParameter("answerId", answerId)
.getSingleResult();
return Optional.of(answer);
}
}
17 changes: 11 additions & 6 deletions src/main/java/com/dnd/gongmuin/answer/service/AnswerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public AnswerDetailResponse registerAnswer(
RegisterAnswerRequest request,
Member member
) {
QuestionPost questionPost = findQuestionPostById(questionPostId);
QuestionPost questionPost = getQuestionPostById(questionPostId);
Answer answer = AnswerMapper.toAnswer(questionPostId, questionPost.isQuestioner(member.getId()), request,
member);
Answer savedAnswer = answerRepository.save(answer);
Expand All @@ -73,8 +73,8 @@ public AnswerDetailResponse chooseAnswer(
Long answerId,
Member member
) {
Answer answer = getAnswerById(answerId);
QuestionPost questionPost = findQuestionPostById(answer.getQuestionPostId());
Answer answer = getAnswerWithMember(answerId);
QuestionPost questionPost = getQuestionPostWithMember(answer.getQuestionPostId());
validateIfQuestioner(member, questionPost);
chooseAnswer(questionPost, answer);

Expand All @@ -99,13 +99,18 @@ private void validateIfQuestionPostExists(Long questionPostId) {
}
}

private Answer getAnswerById(Long answerId) {
return answerRepository.findById(answerId)
private Answer getAnswerWithMember(Long answerId) {
return answerRepository.findByIdWithMember(answerId)
.orElseThrow(() -> new NotFoundException(AnswerErrorCode.NOT_FOUND_ANSWER));
}

private QuestionPost findQuestionPostById(Long questionPostId) {
private QuestionPost getQuestionPostById(Long questionPostId) {
return questionPostRepository.findById(questionPostId)
.orElseThrow(() -> new NotFoundException(QuestionPostErrorCode.NOT_FOUND_QUESTION_POST));
}

private QuestionPost getQuestionPostWithMember(Long questionPostId) {
return questionPostRepository.findByIdWithMember(questionPostId)
.orElseThrow(() -> new NotFoundException(QuestionPostErrorCode.NOT_FOUND_QUESTION_POST));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.dnd.gongmuin.chat_inquiry.dto;

import com.dnd.gongmuin.chat_inquiry.domain.InquiryStatus;
import com.dnd.gongmuin.member.domain.JobGroup;
import com.dnd.gongmuin.member.dto.response.MemberInfo;
import com.dnd.gongmuin.chat_inquiry.domain.ChatInquiry;
import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.response.MemberInfo;
import com.querydsl.core.annotations.QueryProjection;

public record ChatInquiryResponse(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dnd.gongmuin.question_post.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
Expand All @@ -16,7 +17,11 @@ public interface QuestionPostRepository extends JpaRepository<QuestionPost, Long

List<QuestionPost> findAllByMember(Member member);

@Query("select q from QuestionPost q "
+ "join fetch q.member where q.id = :questionPostId")
Optional<QuestionPost> findByIdWithMember(Long questionPostId);

@Modifying(flushAutomatically = true, clearAutomatically = true)
@Query("UPDATE QuestionPost q SET q.member = :member WHERE q.member.id = :memberId")
public void updateQuestionPostsMember(Long memberId, Member member);
void updateQuestionPostsMember(Long memberId, Member member);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.dnd.gongmuin.question_post.repository;

import java.util.Optional;

import org.springframework.stereotype.Repository;

import com.dnd.gongmuin.question_post.domain.QuestionPost;

import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class QuestionPostSimpleQueryRepository {

private final EntityManager em;

public Optional<QuestionPost> findQuestionPostById(Long questionPostId) {
QuestionPost questionPost = em.createQuery(
"select q from QuestionPost q" +
" join fetch q.member m" +
" where q.id = :questionPostId", QuestionPost.class
)
.setParameter("questionPostId", questionPostId)
.getSingleResult();
return Optional.of(questionPost);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.BDDMockito.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -118,9 +123,9 @@ void chooseAnswer() {
QuestionPost questionPost = QuestionPostFixture.questionPost(questionPostId, member);
Answer answer = AnswerFixture.answer(1L, questionPostId);

given(answerRepository.findById(answer.getId()))
given(answerRepository.findByIdWithMember(answer.getId()))
.willReturn(Optional.of(answer));
given(questionPostRepository.findById(questionPost.getId()))
given(questionPostRepository.findByIdWithMember(questionPost.getId()))
.willReturn(Optional.of(questionPost));

//when
Expand All @@ -140,9 +145,9 @@ void chooseAnswer_fail() {
ReflectionTestUtils.setField(questionPost, "reward", member.getCredit() + 1);
Answer answer = AnswerFixture.answer(1L, questionPostId);

given(answerRepository.findById(answer.getId()))
given(answerRepository.findByIdWithMember(answer.getId()))
.willReturn(Optional.of(answer));
given(questionPostRepository.findById(questionPost.getId()))
given(questionPostRepository.findByIdWithMember(questionPost.getId()))
.willReturn(Optional.of(questionPost));

//when & then
Expand All @@ -162,14 +167,66 @@ void chooseAnswer_fail2() {
QuestionPost questionPost = QuestionPostFixture.questionPost(questionPostId, questioner);
Answer answer = AnswerFixture.answer(1L, questionPostId);

given(answerRepository.findById(answer.getId()))
given(answerRepository.findByIdWithMember(answer.getId()))
.willReturn(Optional.of(answer));
given(questionPostRepository.findById(questionPost.getId()))
given(questionPostRepository.findByIdWithMember(questionPost.getId()))
.willReturn(Optional.of(questionPost));

//when & then
assertThatThrownBy(() -> answerService.chooseAnswer(answer.getId(), notQuestioner))
.isInstanceOf(ValidationException.class)
.hasMessageContaining(QuestionPostErrorCode.NOT_AUTHORIZED.getMessage());
}

@Disabled
@DisplayName("[동시에 10_000개의 채택이 일어나 크레딧을 입금 받는다.]")
@Test
void creditHistoryWithOneHundred() throws Exception {
// given
final long threadCount = 10_000L;
final int writerCredit = 10_000_000;
ExecutorService executorService = Executors.newFixedThreadPool(32);
CountDownLatch latch = new CountDownLatch((int)threadCount);

Member writer = MemberFixture.member(1L);
Member answer = MemberFixture.member(2L);
ReflectionTestUtils.setField(writer, "credit", writerCredit);
ReflectionTestUtils.setField(answer, "credit", 0);

List<QuestionPost> questionPosts = new ArrayList<>();
List<Answer> answers = new ArrayList<>();

for (long i = 1L; i <= threadCount; i++) {
QuestionPost questionPost = QuestionPostFixture.questionPost(i, writer);

Answer answer1 = AnswerFixture.answer(questionPost.getId(), answer);
ReflectionTestUtils.setField(answer1, "id", i);
questionPosts.add(questionPost);
answers.add(answer1);

given(answerRepository.findByIdWithMember(i)).willReturn(Optional.of(answer1));
given(questionPostRepository.findByIdWithMember(questionPost.getId()))
.willReturn(Optional.of(questionPost));
}

// when
long startTime = System.currentTimeMillis();
for (long i = 0L; i < threadCount; i++) {
final int index = (int)i;
executorService.submit(() -> {
try {
answerService.chooseAnswer(answers.get(index).getId(), writer);
} finally {
latch.countDown();
}
});
}
latch.await();

long endTime = System.currentTimeMillis();
System.out.println("Execution time: " + (endTime - startTime) + " ms");

// then
assertEquals(answer.getCredit(), writerCredit);
}
}

0 comments on commit 1dd4523

Please sign in to comment.