Skip to content

Commit

Permalink
Merge pull request #214 from Team-Capple/develop
Browse files Browse the repository at this point in the history
[RELEASE] Notification, 답변, 게시글 조회 N+1 문제 해결, 노오프셋 페이징 기법으로 변경
  • Loading branch information
jaewonLeeKOR authored Oct 2, 2024
2 parents 949ab77 + 0edbee9 commit 1d16aa6
Show file tree
Hide file tree
Showing 24 changed files with 140 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,17 @@ public BaseResponse<AnswerResponse.AnswerId> createAnswer(@AuthMember Member mem
}

@Operation(summary = "질문에 대한 답변 조회 API", description = "특정 질문에 대한 답변리스트를 조회하는 API입니다."
+ "pathVariable으로 questionId를 주세요.")
+ "pathVariable으로 questionId를 주세요.<BR>**첫 번째 조회 시 threshold를 비워 보내고, 이후 조회 시 앞선 조회의 반환값으로 받은 threshold를 보내주세요.**")
@GetMapping("/question/{questionId}")
public BaseResponse<SliceResponse<AnswerInfo>> getAnswerList(
@AuthMember Member member,
@Parameter(description = "질문 식별자")
@PathVariable(value = "questionId") Long questionId,
@Parameter(description = "Pull to Refresh 후 마지막 index")
@RequestParam(required = false) Long threshold,
@Parameter(description = "조회할 페이지 번호<br>0부터 시작")
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "이전 조회의 마지막 index")
@RequestParam(required = false, name = "threshold") Long lastIndex,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerService.getAnswerList(member.getId(), questionId, threshold, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(answerService.getAnswerList(member.getId(), questionId, lastIndex, PageRequest.of(0, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "답변 수정 API", description = " 답변 수정 API 입니다." +
Expand All @@ -72,29 +70,25 @@ public BaseResponse<AnswerResponse.AnswerLike> toggleAnswerHeart(@AuthMember Mem
return BaseResponse.onSuccess(answerService.toggleAnswerHeart(member, answerId));
}

@Operation(summary = "작성한 답변 조회 API", description = " 작성한 답변 조회 API 입니다.")
@Operation(summary = "작성한 답변 조회 API", description = " 작성한 답변 조회 API 입니다.<BR>**첫 번째 조회 시 threshold를 비워 보내고, 이후 조회 시 앞선 조회의 반환값으로 받은 threshold를 보내주세요.**")
@GetMapping
public BaseResponse<SliceResponse<MemberAnswerInfo>> getMemberAnswer(
@AuthMember Member member,
@Parameter(description = "Pull to Refresh 후 마지막 index")
@RequestParam(required = false) Long threshold,
@Parameter(description = "조회할 페이지 번호<br>0부터 시작")
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "이전 조회의 마지막 index")
@RequestParam(required = false, name = "threshold") Long lastIndex,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerService.getMemberAnswer(member, threshold, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(answerService.getMemberAnswer(member, lastIndex, PageRequest.of(0, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "좋아한 답변 조회 API", description = " 좋아한 답변 조회 API 입니다.")
@Operation(summary = "좋아한 답변 조회 API", description = " 좋아한 답변 조회 API 입니다.<BR>**첫 번째 조회 시 threshold를 비워 보내고, 이후 조회 시 앞선 조회의 반환값으로 받은 threshold를 보내주세요.**")
@GetMapping("/heart")
public BaseResponse<SliceResponse<MemberAnswerInfo>> getMemberHeartAnswer(
@AuthMember Member member,
@Parameter(description = "Pull to Refresh 후 마지막 index")
@RequestParam(required = false) Long threshold,
@Parameter(description = "조회할 페이지 번호<br>0부터 시작")
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "이전 조회의 마지막 index")
@RequestParam(required = false, name = "threshold") Long lastIndex,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerService.getMemberHeartAnswer(member, threshold, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(answerService.getMemberHeartAnswer(member, lastIndex, PageRequest.of(0, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@ public class AnswerRDBDao {
public interface AnswerInfoInterface {
public Answer getAnswer();
public Boolean getIsReported();
public Long getWriterId();
public String getWriterProfileImage();
public String getWriterNickname();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.server.capple.domain.answer.mapper;

import com.server.capple.domain.answer.dao.AnswerRDBDao.AnswerInfoInterface;
import com.server.capple.domain.answer.dto.AnswerRequest;
import com.server.capple.domain.answer.dto.AnswerResponse.AnswerInfo;
import com.server.capple.domain.answer.dto.AnswerResponse.MemberAnswerInfo;
Expand All @@ -8,8 +9,6 @@
import com.server.capple.domain.question.entity.Question;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class AnswerMapper {
public Answer toAnswerEntity(AnswerRequest request, Member member, Question question) {
Expand All @@ -20,17 +19,17 @@ public Answer toAnswerEntity(AnswerRequest request, Member member, Question ques
.build();
}

public AnswerInfo toAnswerInfo(Answer answer, Long memberId, Boolean isReported, Boolean isLiked, Boolean isMine) {
public AnswerInfo toAnswerInfo(AnswerInfoInterface answerInfoDto, Long memberId, Boolean isLiked) {
return AnswerInfo.builder()
.answerId(answer.getId())
.writerId(answer.getMember().getId())
.profileImage(answer.getMember().getProfileImage())
.nickname(answer.getMember().getNickname())
.content(answer.getContent())
.isMine(isMine)
.isReported(isReported)
.answerId(answerInfoDto.getAnswer().getId())
.writerId(answerInfoDto.getWriterId())
.profileImage(answerInfoDto.getWriterProfileImage())
.nickname(answerInfoDto.getWriterNickname())
.content(answerInfoDto.getAnswer().getContent())
.isMine(answerInfoDto.getWriterId().equals(memberId))
.isReported(answerInfoDto.getIsReported())
.isLiked(isLiked)
.writeAt(answer.getCreatedAt().toString())
.writeAt(answerInfoDto.getAnswer().getCreatedAt().toString())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,24 @@
public interface AnswerRepository extends JpaRepository<Answer, Long> {
Optional<Answer> findById(Long answerId);

Slice<Answer> findByIdInAndIdIsLessThanEqual(Set<Long> answerIds, Long lastIndex, Pageable pageable);
@Query("SELECT a FROM Answer a WHERE (a.id < :lastIndex OR :lastIndex IS NULL) AND a.id IN :answerIds")
Slice<Answer> findByIdInAndIdIsLessThan(Set<Long> answerIds, Long lastIndex, Pageable pageable);

boolean existsByQuestionAndMember(Question question, Member member);

@Query("SELECT a AS answer, (r IS NOT NULL) AS isReported " +
@Query("SELECT a AS answer, " +
"(r IS NOT NULL) AS isReported, " +
"a.member.id AS writerId, " +
"a.member.profileImage AS writerProfileImage, " +
"a.member.nickname AS writerNickname "+
"FROM Answer a " +
"LEFT JOIN " +
"Report r ON r.answer = a " +
"WHERE a.id <= :lastIndex AND a.question.id = :questionId")
"WHERE (a.id < :lastIndex OR :lastIndex IS NULL) AND a.question.id = :questionId")
Slice<AnswerInfoInterface> findByQuestion(@Param("questionId") Long questionId, Long lastIndex, Pageable pageable);

Slice<Answer> findByMemberAndIdIsLessThanEqual(@Param("member") Member member, Long lastIndex, Pageable pageable);
@Query("SELECT a FROM Answer a WHERE (a.id < :lastIndex OR :lastIndex IS NULL) AND a.member = :member")
Slice<Answer> findByMemberAndIdIsLessThan(@Param("member") Member member, Long lastIndex, Pageable pageable);

@Query("SELECT COUNT(a) FROM Answer a WHERE a.question.id = :questionId")
Integer getAnswerCountByQuestionId(Long questionId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,26 +99,22 @@ public AnswerLike toggleAnswerHeart(Member loginMember, Long answerId) {

@Override
public SliceResponse<AnswerInfo> getAnswerList(Long memberId, Long questionId, Long lastIndex, Pageable pageable) {
lastIndex = getLastIndex(lastIndex);
Slice<AnswerInfoInterface> answerInfoSliceInterface = answerRepository.findByQuestion(questionId, lastIndex, pageable);
lastIndex = getLastIndexFromAnswerInfoInterface(lastIndex, answerInfoSliceInterface);
lastIndex = getLastIndexFromAnswerInfoInterface(answerInfoSliceInterface);
return SliceResponse.toSliceResponse(answerInfoSliceInterface, answerInfoSliceInterface.getContent().stream().map(
answerInfoDto -> answerMapper.toAnswerInfo(
answerInfoDto.getAnswer(),
answerInfoDto,
memberId,
answerInfoDto.getIsReported(),
answerHeartRedisRepository.isMemberLikedAnswer(memberId, answerInfoDto.getAnswer().getId()),
answerInfoDto.getAnswer().getMember().getId().equals(memberId)
answerHeartRedisRepository.isMemberLikedAnswer(memberId, answerInfoDto.getAnswer().getId())
)
).toList(), lastIndex.toString(), answerCountService.getQuestionAnswerCount(questionId));
}

// 유저가 작성한 답변 조회
@Override
public SliceResponse<MemberAnswerInfo> getMemberAnswer(Member member, Long lastIndex, Pageable pageable) {
lastIndex = getLastIndex(lastIndex);
Slice<Answer> answerSlice = answerRepository.findByMemberAndIdIsLessThanEqual(member, lastIndex, pageable);
lastIndex = getLastIndexFromAnswer(lastIndex, answerSlice);
Slice<Answer> answerSlice = answerRepository.findByMemberAndIdIsLessThan(member, lastIndex, pageable);
lastIndex = getLastIndexFromAnswer(answerSlice);
return SliceResponse.toSliceResponse(
answerSlice, answerSlice.getContent().stream()
.map(answer -> answerMapper.toMemberAnswerInfo(
Expand All @@ -132,9 +128,8 @@ public SliceResponse<MemberAnswerInfo> getMemberAnswer(Member member, Long lastI
// 유저가 좋아한 답변 조회 //TODO 좋아요니까 좋아요한 순으로 정렬해야할거같은데 Answer의 createAt으로 하고 있음
@Override
public SliceResponse<MemberAnswerInfo> getMemberHeartAnswer(Member member, Long lastIndex, Pageable pageable) {
lastIndex = getLastIndex(lastIndex);
Slice<Answer> answerSlice = answerRepository.findByIdInAndIdIsLessThanEqual(answerHeartRedisRepository.getMemberHeartsAnswer(member.getId()), lastIndex, pageable);
lastIndex = getLastIndexFromAnswer(lastIndex, answerSlice);
Slice<Answer> answerSlice = answerRepository.findByIdInAndIdIsLessThan(answerHeartRedisRepository.getMemberHeartsAnswer(member.getId()), lastIndex, pageable);
lastIndex = getLastIndexFromAnswer(answerSlice);
return SliceResponse.toSliceResponse(answerSlice, answerSlice.getContent().stream()
.map(answer -> answerMapper.toMemberAnswerInfo(
answer,
Expand All @@ -159,20 +154,16 @@ public Answer findAnswer(Long answerId) {
);
}

private Long getLastIndex(Long lastIndex) {
return lastIndex == null ? Long.MAX_VALUE : lastIndex;
private Long getLastIndexFromAnswerInfoInterface(Slice<AnswerInfoInterface> answerInfoSliceInterface) {
if(answerInfoSliceInterface.hasContent())
return answerInfoSliceInterface.stream().map(AnswerInfoInterface::getAnswer).map(Answer::getId).min(Long::compareTo).get();
return -1L;
}

private Long getLastIndexFromAnswerInfoInterface(Long lastIndex, Slice<AnswerInfoInterface> answerInfoSliceInterface) {
if(answerInfoSliceInterface.hasContent() && lastIndex == Long.MAX_VALUE)
return answerInfoSliceInterface.stream().map(AnswerInfoInterface::getAnswer).map(Answer::getId).max(Long::compareTo).get();
return lastIndex;
}

private Long getLastIndexFromAnswer(Long lastIndex, Slice<Answer> answerSlice) {
if (answerSlice.hasContent() && lastIndex == Long.MAX_VALUE)
return answerSlice.stream().map(Answer::getId).max(Long::compareTo).get();
return lastIndex;
private Long getLastIndexFromAnswer(Slice<Answer> answerSlice) {
if (answerSlice.hasContent())
return answerSlice.stream().map(Answer::getId).min(Long::compareTo).get();
return -1L;
}

@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,48 +49,47 @@ private BaseResponse<BoardId> updateBoard(@AuthMember Member member,
return BaseResponse.onSuccess(boardService.updateBoard(member, request.getBoardId(), request.getContent()));
}

@Operation(summary = "카테고리별 게시글 조회 with REDIS API(프론트 사용 X, 성능 테스트 용)", description = "카테고리별 게시글을 조회합니다.")
@Operation(summary = "카테고리별 게시글 조회 with REDIS API(프론트 사용 X, 성능 테스트 용)", description = "카테고리별 게시글을 조회합니다.<BR>**첫 번째 조회 시 threshold를 비워 보내고, 이후 조회 시 앞선 조회의 반환값으로 받은 threshold를 보내주세요.**")
@ApiResponses(value = {
@ApiResponse(responseCode = "COMMON200", description = "성공"),
})
@GetMapping("/redis")
public BaseResponse<SliceResponse<BoardInfo>> getBoardsByBoardTypeWithRedis(
@AuthMember Member member,
@RequestParam(name = "boardType", required = false) BoardType boardType,
@Parameter(description = "Pull to Refresh 후 마지막 index")
@RequestParam(required = false) Long threshold,
@RequestParam(defaultValue = "0", required = false) Integer pageNumber, @RequestParam(defaultValue = "1000", required = false) Integer pageSize
@Parameter(description = "이전 조회의 마지막 index")
@RequestParam(required = false, name = "threshold") Long lastIndex,
@RequestParam(defaultValue = "1000", required = false) Integer pageSize
) {
return BaseResponse.onSuccess(boardService.getBoardsByBoardTypeWithRedis(member, boardType, threshold, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(boardService.getBoardsByBoardTypeWithRedis(member, boardType, lastIndex, PageRequest.of(0, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "카테고리별 게시글 조회", description = "카테고리별 게시글을 조회합니다.")
@Operation(summary = "카테고리별 게시글 조회", description = "카테고리별 게시글을 조회합니다.<BR>**첫 번째 조회 시 threshold를 비워 보내고, 이후 조회 시 앞선 조회의 반환값으로 받은 threshold를 보내주세요.**")
@ApiResponses(value = {
@ApiResponse(responseCode = "COMMON200", description = "성공"),
})
@GetMapping()
public BaseResponse<SliceResponse<BoardInfo>> getBoardsByBoardType(
@AuthMember Member member,
@RequestParam(name = "boardType", required = false) BoardType boardType,
@Parameter(description = "Pull to Refresh 후 마지막 index")
@RequestParam(required = false) Long threshold,
@RequestParam(defaultValue = "0", required = false) Integer pageNumber, @RequestParam(defaultValue = "1000", required = false) Integer pageSize
@Parameter(description = "이전 조회의 마지막 index")
@RequestParam(required = false, name = "threshold") Long lastIndex,
@RequestParam(defaultValue = "1000", required = false) Integer pageSize
) {
return BaseResponse.onSuccess(boardService.getBoardsByBoardType(member, boardType, threshold, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(boardService.getBoardsByBoardType(member, boardType, lastIndex, PageRequest.of(0, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "게시글 검색 API", description = "게시글을 검색합니다. 자유게시판에서만 검색이 가능합니다.")
@Operation(summary = "게시글 검색 API", description = "게시글을 검색합니다. 자유게시판에서만 검색이 가능합니다.<BR>**첫 번째 조회 시 threshold를 비워 보내고, 이후 조회 시 앞선 조회의 반환값으로 받은 threshold를 보내주세요.**")
@ApiResponses(value = {
@ApiResponse(responseCode = "COMMON200", description = "성공"),
})
@GetMapping("/search")
public BaseResponse<SliceResponse<BoardInfo>> searchBoardsByKeyword(
@AuthMember Member member, @RequestParam(name = "keyword") String keyword,
@Parameter(description = "Pull to Refresh 후 마지막 index")
@RequestParam(required = false) Long threshold,
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "이전 조회의 마지막 index")
@RequestParam(required = false, name = "threshold") Long lastIndex,
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(boardService.searchBoardsByKeyword(member, keyword, threshold, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(boardService.searchBoardsByKeyword(member, keyword, lastIndex, PageRequest.of(0, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public interface BoardInfoInterface {
Board getBoard();
Boolean getIsLike();
Boolean getIsMine();
String getWriterNickname();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ public Board toBoard(Member member, BoardType boardType, String content) {
}

//redis
public BoardInfo toBoardInfo(Board board, Integer boardHeartsCount, Boolean isLiked, Boolean isMine) {
public BoardInfo toBoardInfo(Board board, String writerNickname, Integer boardHeartsCount, Boolean isLiked, Boolean isMine) {
return BoardInfo.builder()
.boardId(board.getId())
.writerId(board.getWriter().getId())
.writerNickname(board.getWriter().getNickname())
.writerNickname(writerNickname)
.content(board.getContent())
.heartCount(boardHeartsCount)
.commentCount(board.getCommentCount())
Expand All @@ -37,11 +37,11 @@ public BoardInfo toBoardInfo(Board board, Integer boardHeartsCount, Boolean isLi
}

//rdb
public BoardInfo toBoardInfo(Board board, Boolean isLiked, Boolean isMine) {
public BoardInfo toBoardInfo(Board board, String writerNickname, Boolean isLiked, Boolean isMine) {
return BoardInfo.builder()
.boardId(board.getId())
.writerId(board.getWriter().getId())
.writerNickname(board.getWriter().getNickname())
.writerNickname(writerNickname)
.content(board.getContent())
.heartCount(board.getHeartCount())
.commentCount(board.getCommentCount())
Expand Down
Loading

0 comments on commit 1d16aa6

Please sign in to comment.