Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: 게시글 목록 조회 성능 최적화(#76) #81

Merged
merged 9 commits into from
Nov 28, 2024
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package aimo.backend.domains.comment.controller;

import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
Expand All @@ -16,6 +19,7 @@
import aimo.backend.domains.comment.dto.parameter.UpdateParentCommentParameter;
import aimo.backend.domains.comment.dto.request.SaveParentCommentRequest;
import aimo.backend.domains.comment.dto.request.UpdateParentCommentRequest;
import aimo.backend.domains.comment.dto.response.FindCommentsResponse;
import aimo.backend.domains.comment.service.ParentCommentService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -69,4 +73,13 @@ public ResponseEntity<DataResponse<Void>> deleteParentComment(

return ResponseEntity.ok(DataResponse.ok());
}

@GetMapping("/{postId}/comments")
public ResponseEntity<DataResponse<List<FindCommentsResponse>>> findComments(
@PathVariable("postId") Long postId
) {
Long memberId = MemberLoader.getMemberId();

return ResponseEntity.ok(DataResponse.from(parentCommentService.findComments(memberId, postId)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package aimo.backend.domains.comment.dto.response;

import java.time.LocalDateTime;
import java.util.List;

import aimo.backend.domains.comment.entity.ChildComment;
import aimo.backend.domains.comment.entity.ParentComment;
import aimo.backend.domains.member.entity.Member;
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
public class FindCommentsResponse {

private final Boolean isMine;
private final Boolean isLiked;
private final Long commentId;
private final String content;
private final String nickname;
private final Integer likesCount;
private final LocalDateTime createdAt;
private final List<ChildCommentDto> childComments;

public static FindCommentsResponse of(Member member, ParentComment parentComment) {
boolean isMine = parentComment.getMember() == member;
boolean isLiked = parentComment.getParentCommentLikes()
.stream()
.anyMatch(like -> like.getMember() == member);
List<ChildCommentDto> childCommentDtos = parentComment.getChildComments()
.stream()
.map(childComment -> ChildCommentDto.of(member, childComment))
.toList();

return new FindCommentsResponse(
isMine,
isLiked,
parentComment.getId(),
parentComment.getContent(),
parentComment.getNickname(),
parentComment.getLikesCount(),
parentComment.getCreatedAt(),
childCommentDtos
);
}


@Data
@AllArgsConstructor
private static class ChildCommentDto {
private final Boolean isMine;
private final Boolean isLiked;
private final Long childCommentId;
private final String content;
private final String nickname;
private final Integer likesCount;
private final LocalDateTime createdAt;

public static ChildCommentDto of(Member member, ChildComment childComment) {
boolean isMine = childComment.getMember() == member;
boolean isLiked = childComment.getChildCommentLikes()
.stream()
.anyMatch(like -> like.getMember() == member);

return new ChildCommentDto(
isMine,
isLiked,
childComment.getId(),
childComment.getContent(),
childComment.getNickname(),
childComment.getLikesCount(),
childComment.getCreatedAt()
);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package aimo.backend.domains.comment.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import aimo.backend.domains.comment.entity.ChildComment;
Expand All @@ -10,5 +8,5 @@ public interface ChildCommentRepository extends JpaRepository<ChildComment, Long

Boolean existsByIdAndMember_Id(Long id, Long memberId);

List<ChildComment> findByMemberId(Long memberId);
Integer countByPost_Id(Long postId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import aimo.backend.domains.comment.entity.ParentComment;

Expand All @@ -11,4 +12,11 @@ public interface ParentCommentRepository extends JpaRepository<ParentComment, Lo
Boolean existsByIdAndMember_Id(Long id, Long memberId);

List<ParentComment> findAllByMemberId(Long memberId);

List<ParentComment> findAllByPost_Id(Long postId);

@Query("SELECT DISTINCT p FROM ParentComment p JOIN FETCH p.childComments WHERE p.post.id = :postId")
List<ParentComment> findAllByPost_IdWithChildComments(Long postId);

Integer countByPost_Id(Long postId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import static aimo.backend.common.exception.ErrorCode.*;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import aimo.backend.common.exception.ApiException;
import aimo.backend.domains.comment.dto.parameter.DeleteParentCommentParameter;
import aimo.backend.domains.comment.dto.parameter.SaveParentCommentParameter;
import aimo.backend.domains.comment.dto.parameter.UpdateParentCommentParameter;
import aimo.backend.domains.comment.dto.response.FindCommentsResponse;
import aimo.backend.domains.comment.entity.ParentComment;
import aimo.backend.domains.comment.repository.ParentCommentRepository;
import aimo.backend.domains.member.entity.Member;
Expand Down Expand Up @@ -97,4 +100,18 @@ public void deleteIfParentCommentIsDeletedAndChildrenIsEmpty(ParentComment paren

parentCommentRepository.delete(parentComment);
}

public List<FindCommentsResponse> findComments(Long memberId, Long postId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> ApiException.from(MEMBER_NOT_FOUND));

// 부모 댓글 fetch join으로 조회
List<ParentComment> parentComments = parentCommentRepository.findAllByPost_IdWithChildComments(postId);

List<FindCommentsResponse> responses = parentComments.stream()
.map(parentComment -> FindCommentsResponse.of(member, parentComment))
.toList();

return responses;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package aimo.backend.domains.like.entity;

import static jakarta.persistence.GenerationType.*;
import static lombok.AccessLevel.*;

import aimo.backend.domains.member.entity.Member;
import aimo.backend.domains.post.entity.Post;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.Builder;
Expand All @@ -22,19 +21,20 @@
@UniqueConstraint(columnNames = {"post_id", "member_id"})
})
@NoArgsConstructor(access = PROTECTED)
public class PostLike extends Like {
public class PostLike {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "like_id")
private Long id;

@Builder
private PostLike(Post post, Member member) {
super(member);
this.post = post;
}
private Long postId;

public static PostLike from(Member member, Post post) {
return PostLike.builder().member(member).post(post).build();
private Long memberId;

@Builder
private PostLike (Long postId, Long memberId) {
this.postId = postId;
this.memberId = memberId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

public interface PostLikeRepository extends JpaRepository<PostLike, Long> {

void deleteByMember_IdAndPost_Id(Long id, Long postId);
void deleteByMemberIdAndPostId(Long id, Long postId);

boolean existsByPostIdAndMemberId(Long postId, Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import aimo.backend.domains.like.entity.PostLike;
import aimo.backend.domains.like.model.LikeType;
import aimo.backend.domains.like.repository.PostLikeRepository;
import aimo.backend.domains.member.entity.Member;
import aimo.backend.domains.member.repository.MemberRepository;
import aimo.backend.domains.like.dto.parameter.LikePostParameter;
import aimo.backend.domains.post.entity.Post;
Expand All @@ -33,20 +32,44 @@ public void likePost(LikePostParameter parameter) {

Post post = postRepository.findById(postId)
.orElseThrow(() -> ApiException.from(POST_NOT_FOUND));
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> ApiException.from(MEMBER_NOT_FOUND));

// 존재 확인
validatePostExist(postId);
validateMemberExist(memberId);

if (likeType == LikeType.LIKE) {
// 라이크가 존재하면 중복 등록 방지
if (postLikeRepository.existsByPostIdAndMemberId(postId, memberId))
return;

PostLike postLike = PostLike.from(member, post);
// 라이크 등록
PostLike postLike = PostLike.builder()
.postId(postId)
.memberId(memberId)
.build();
postLikeRepository.save(postLike);

// 포스트 라이크 수 증가
post.increasePostLikesCount();
return ;
}

// 라이크가 이미 존재하면 삭제
postLikeRepository.deleteByMember_IdAndPost_Id(member.getId(), postId);
postLikeRepository.deleteByMemberIdAndPostId(memberId, postId);
post.decreasePostLikesCount();
}

// 글 존재 여부
private void validatePostExist(Long postId) {
if (!postRepository.existsById(postId)) {
throw ApiException.from(POST_NOT_FOUND);
}
}

// 멤버 존재 여부
private void validateMemberExist(Long memberId) {
if (!memberRepository.existsById(memberId)) {
throw ApiException.from(MEMBER_NOT_FOUND);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
package aimo.backend.domains.member.entity;

import static lombok.AccessLevel.*;

import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
@NoArgsConstructor(access = PROTECTED)
@AllArgsConstructor
@RedisHash(value = "accessToken", timeToLive = 3600)
public class AccessToken {
@Id
private final Long memberId;
private Long memberId;
@Indexed
private final String accessToken;
private String accessToken;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package aimo.backend.domains.member.entity;

import static lombok.AccessLevel.*;

import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@AllArgsConstructor
@NoArgsConstructor(access = PROTECTED)
@RedisHash(value = "refreshToken", timeToLive = 1209600)
public class RefreshToken {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,33 +43,4 @@ public static FindCommentedPostsByIdRequest of(
commentedAt
);
}

public static FindCommentedPostsByIdRequest from(ParentComment parentComment) {
Post post = parentComment.getPost();

final Float plaintiffVotesCount = (float)post.getPlaintiffVotesCount();
final Float defendantVotesCount = (float)post.getDefendantVotesCount();
final Float votesCount = (float)post.getVotesCount();

// 투표율 계산
float voteRatePlaintiff = 0f;
float voteRateDefendant = 0f;
if (votesCount != 0) {
voteRatePlaintiff = plaintiffVotesCount / votesCount;
voteRateDefendant = defendantVotesCount / votesCount;
}

return new FindCommentedPostsByIdRequest(
post.getId(),
post.getTitle(),
post.getPreview(),
post.getPostLikesCount(),
post.getPostViewsCount(),
post.getCommentsCount(),
voteRatePlaintiff,
voteRateDefendant,
post.getCreatedAt(),
post.getCreatedAt()
);
}
}
Loading
Loading