[ํ๋ก์ ํธ] ๋๊ธ๊ณผ ๋๋๊ธ ๊ด๊ณ ๋ก์ง ์ํฐํฐ ํ๋๋ก ๊ตฌํํ๊ธฐ (JPA)Back-end/Project2024. 9. 6. 16:04
Table of Contents
โ Git Issue
https://github.com/ellaCoo/sns/issues/6
https://github.com/ellaCoo/sns/pull/31

๐ ์ํฉ

→ Post์ ํด๋นํ๋ ๋๊ธ์ ๋๋๊ธ๊น์ง ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ธฐ ์ํด parent_comment_id ์ปฌ๋ผ์ ์ถ๊ฐํจ
โ๏ธ PostId๋ก [ ํฌ์คํธ์ ๋ณด + ํด๋น ํฌ์คํธ ๋๊ธ + ์ ์ ์ ์ข์์ ์ ๋ณด ] ๊ฐ์ ธ์ฌ ๋, PostComment(๋๊ธ)๊ณผ ๋๋๊ธ์ createdAt(์์ฑ์ผ์)๋ก ์ ๋ ฌํ์ฌ ํจ๊ป Response ๊ฐ์ฒด์ ๋ด๊ธฐ
1. Post ์ํฐํฐ ์์ Set<PostComment> postComments = new LinkedHashSet<>();๋ฅผ ์ ์ํ์ฌ ๊ด๊ณ ๋งตํ
2. PostService ์์ postRepository.findById(postId) ๋ก Post ๊ฐ์ ธ์ค๊ธฐ
3. PostWithLikesAndHashtagsAndCommentsDto ์ fromEntity ๋ฉ์๋๋ก
Post ์ Set<PostComment> postComments = new LinkedHashSet<>();
⇒ PostWithLikesAndHashtagsAndCommentsDto ์ Set<PostCommentDto> postCommentDtos ๋ก ๋ณํ
entity.getPostComments().stream()
.map(PostCommentDto::fromEntity)
.collect(Collectors.toCollection(LinkedHashSet::new))
⇒ ์ฌ๊ธฐ๊น์ง๋ Post์ํฐํฐ์ ์ ์๋์ด ์๋ @OrderBy ๋ด์ฉ์ธ ์์ฑ์ผ์ ๋ด๋ฆผ์ฐจ์์ผ๋ก๋ง ์ ๋ ฌ๋์ด ์๋ค.
@OrderBy("createdAt DESC")
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY) // ์ฐ๊ด๋ ์ํฐํฐ๋ ๋ชจ๋ ์์์ฑ ์ ์ด
private Set<PostComment> postComments = new LinkedHashSet<>();
4. Dto๋ฅผ PostWithLikesAndHashtagAndCommentsResponse ๋ก ๋ณํํ๋ ๊ณผ์ ์์
parentId๋ฅผ ํ์ฉํ์ฌ ๋ถ๋ชจ-์์ ํํ๋ก ๊ฐ๊ณตํ๋ค.
// PostWithLikesAndHashtagAndCommentsResponse
public static PostWithLikesAndHashtagAndCommentsResponse fromDto(PostWithLikesAndHashtagsAndCommentsDto dto, String userId) {
Set<String> likes = dto.likeDtos().stream().map(likeDto -> likeDto.userId()).collect(Collectors.toSet());
Set<String> hashtags = dto.hashtagDtos().stream().map(hashtagDto -> hashtagDto.hashtagName()).collect(Collectors.toSet());
return new PostWithLikesAndHashtagAndCommentsResponse(
PostResponse.fromDto(dto.postDto()),
dto.userAccountDto().userId(),
null != userId && likes.contains(userId),
likes,
organizeChildComments(dto.postCommentDtos()), // โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ
hashtags
);
}
private static Set<PostCommentResponse> **organizeChildComments**(Set<PostCommentDto> dtos) {
// Post์ ๋๊ธ๋ค์ {commentId : PostCommentResponse} ํํ๋ก ๋ณํํ๋ค.
Map<Long, PostCommentResponse> map = dtos.stream()
.map(PostCommentResponse::fromDto)
.collect(Collectors.toMap(PostCommentResponse::id, Function.identity()));
map.values().stream()
.filter(comment -> comment.parentCommentId() != null) // ์์๋๊ธ์ธ ์ ๋ค ๋ฝ์๋ด๊ธฐ
.forEach(comment -> {
PostCommentResponse parentComment = map.get(comment.parentCommentId());
parentComment.childComments().add(comment); // ์์๋๊ธ์ ๋ถ๋ชจ๋๊ธ์ ํด๋น ์์๋๊ธ ์ถ๊ฐ
});
return map.values().stream()
.filter(comment -> comment.parentCommentId() == null) // ๋ถ๋ชจ๋๊ธ์ธ ์ ๋ค๋ง ๋ฝ์๋ด๊ธฐ (์์์ ์์๋๊ธ ์ถ๊ฐ ์๋ฃ)
.collect(Collectors.toCollection(() ->
new TreeSet<>(Comparator
.comparing(PostCommentResponse::createdAt)
.reversed()
.thenComparingLong(PostCommentResponse::id)
)
));
}
//PostCommentResponse
public record PostCommentResponse(
Long id,
String content,
LocalDateTime createdAt,
String email,
String nickname,
String userId,
Long parentCommentId,
Set<PostCommentResponse> childComments
) {
public static PostCommentResponse of(Long id, String content, LocalDateTime createdAt, String email, String nickname, String userId, Long parentCommentId) {
Comparator<PostCommentResponse> childCommentComparator = Comparator
.comparing(PostCommentResponse::createdAt); // ์์ฑ์๊ฐ ์ค๋ฆ์ฐจ์
return new PostCommentResponse(id, content, createdAt, email, nickname, userId, parentCommentId, new TreeSet<>(childCommentComparator));
}
๐ ๊ตฌํ์ ํ๊ณ ๋๋๊ธ ๊ธฐ๋ฅ ํ ์คํธ๋ฅผ ์งํํ๋๋ฐ, ๋์ผํ ํฌ์คํธ์ ๋์ผํ parentId์ ๊ฐ์ง ๋๋๊ธ์ด 2๊ฐ๊ฐ ๋์์ผ ํ๋๋ฐ ํ๋๋ง ๋์ค๋ ๋๊ธ ๋๋ฝํ์์ ๋ฐ๊ฒฌํจ
์์ธ
๋ง์ฝ createdAt๋ง์ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌํ๊ณ , ๋์ผํ createdAt ๊ฐ์ ๊ฐ์ง ์ฌ๋ฌ ๋๊ธ์ด ์กด์ฌํ๋ค๋ฉด, ์ด๋ค ๊ฐ์ ๋น๊ตํ ์ ์๋ ์ถ๊ฐ์ ์ธ ๊ธฐ์ค(ID ๋ฑ)์ด ์๊ธฐ ๋๋ฌธ์, ๋์ผํ createdAt์ ๊ฐ์ง ๋๊ธ๋ค ์ค ์ค์ง ํ๋์ ๋๊ธ๋ง TreeSet์ ์ ์ฅ๋จ
TreeSet์ ์์๋ฅผ ์ ์ฅํ ๋ ์ ๋ ฌ ๊ธฐ์ค์ด ๋์ผํ ๊ฐ์ฒด๋ฅผ ์ค๋ณต์ผ๋ก ๊ฐ์ฃผํ๊ณ , ์ค๋ณต๋ ๊ฐ์ฒด๋ ์ ์ฅํ์ง ์์.
⇒ Comparator<PostCommentResponse> childCommentComparator ์์
์์ฑ์๊ฐ ๋์ผํ ๋ ์ ๋ ฌ๊ธฐ์ค์ id๋ก ์ค์ ํ์ฌ ๋๊ธ ๋๋ฝ์ ํผํ์!
//PostCommentResponse
public static PostCommentResponse of(Long id, String content, LocalDateTime createdAt, String email, String nickname, String userId, Long parentCommentId) {
Comparator<PostCommentResponse> childCommentComparator = Comparator
.comparing(PostCommentResponse::createdAt) // ์์ฑ์๊ฐ ์ค๋ฆ์ฐจ์
.thenComparingLong(PostCommentResponse::id); // ์์ฑ์๊ฐ ๋์ผํ ๋ ์ ๋ ฌ ๊ธฐ์ค
return new PostCommentResponse(id, content, createdAt, email, nickname, userId, parentCommentId, new TreeSet<>(childCommentComparator));
}
TreeSet & Comparator ๋
TreeSet
- ํน์ง
์ค๋ณต ํ์ฉ X: ๋์ผํ ๊ฐ์ ๊ฐ์ง๋ ๋ ๊ฐ์ ์์๋ฅผ ํ์ฉํ์ง ์์
์๋ ์ ๋ ฌ: ์ฝ์ ์์์ ๊ด๊ณ์์ด ์์๋ค์ด ์๋์ผ๋ก ์ ๋ ฌ๋จ
- ์ฅ์
์ ๋ ฌ๋ ๊ฒฐ๊ณผ: ํญ์ ์ ๋ ฌ๋ ์ํ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์งํ๋ฏ๋ก, ํ์์ด ์ฝ๊ณ ๋น ๋ฆ
์ค๋ณต๋ ๊ฐ ์ ๊ฑฐ: ์ค๋ณต๋ ๊ฐ์ ์๋์ผ๋ก ๊ฑธ๋ฌ๋ด๊ธฐ ๋๋ฌธ์ ์ ์ฉํจ
- ๋จ์
์ฝ์ ์๋: HashSet์ฒ๋ผ O(1)์ด ์๋๋ผ, ํธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ ์งํ๋ ๋ฐ O(log n)์ ์๊ฐ์ด ์์๋จ
๋ฉ๋ชจ๋ฆฌ: ํธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ ์งํด์ผ ํ๋ฏ๋ก, HashSet๋ณด๋ค ๋ ๋ง์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ ์์
Comparator
Comparator๋ ๊ฐ์ฒด๋ฅผ ๋น๊ตํ๊ณ ์ ๋ ฌ ์์๋ฅผ ์ ์ํ๋ ์ธํฐํ์ด์ค
์ ์ฒด ์์ค์ฝ๋
๐พPostController
PostWithLikesAndHashtagAndCommentsResponse response = PostWithLikesAndHashtagAndCommentsResponse
.fromDto(postService.getPostWithLikesAndHashtagsAndComments(postId), user);
๐พPostService
@Transactional(readOnly = true)
public PostWithLikesAndHashtagsAndCommentsDto getPostWithLikesAndHashtagsAndComments(Long postId) {
return postRepository.findById(postId)
.map(PostWithLikesAndHashtagsAndCommentsDto::fromEntity)
.orElseThrow(() -> new EntityNotFoundException("ํฌ์คํธ๊ฐ ์์ต๋๋ค - postId: " + postId));
}
๐พPostWithLikesAndHashtagsAndCommentsDto
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
public record PostWithLikesAndHashtagsAndCommentsDto(
PostDto postDto,
UserAccountDto userAccountDto,
Set<LikeDto> likeDtos,
Set<PostCommentDto> postCommentDtos,
Set<HashtagDto> hashtagDtos
) {
public static PostWithLikesAndHashtagsAndCommentsDto of(PostDto postDto, UserAccountDto userAccountDto, Set<LikeDto> likeDtos, Set<PostCommentDto> postCommentDtos, Set<HashtagDto> hashtagDtos) {
return new PostWithLikesAndHashtagsAndCommentsDto(postDto, userAccountDto, likeDtos, postCommentDtos, hashtagDtos);
}
public static PostWithLikesAndHashtagsAndCommentsDto fromEntity(Post entity) {
return new PostWithLikesAndHashtagsAndCommentsDto(
PostDto.fromEntity(entity),
UserAccountDto.fromEntity(entity.getUserAccount()),
entity.getLikes().stream()
.map(LikeDto::fromEntity)
.collect(Collectors.toCollection(LinkedHashSet::new))
,
entity.getPostComments().stream()
.map(PostCommentDto::fromEntity)
.collect(Collectors.toCollection(LinkedHashSet::new))
,
entity.getPostHashtags().stream()
.map(postHashtag -> postHashtag.getHashtag())
.map(HashtagDto::fromEntity)
.collect(Collectors.toSet())
);
}
}
๐พPost
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.*;
@Getter
@Entity
public class Post extends AuditingFields {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter
@JoinColumn(name = "userId")
@ManyToOne(optional = false, fetch = FetchType.LAZY)
private UserAccount userAccount;
@Setter
@Column(nullable = false)
private String title;
@Setter
@Column(nullable = false, length = 10000)
private String content;
@OrderBy("createdAt DESC")
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY) // ์ฐ๊ด๋ ์ํฐํฐ๋ ๋ชจ๋ ์์์ฑ ์ ์ด
private Set<PostComment> postComments = new LinkedHashSet<>();
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Like> likes = new LinkedHashSet<>();
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<PostHashtag> postHashtags = new LinkedHashSet<>();
protected Post() {}
private Post(UserAccount userAccount, String title, String content) {
this.userAccount = userAccount;
this.title = title;
this.content = content;
}
public static Post of(UserAccount userAccount, String title, String content) {
return new Post(userAccount, title, content);
}
@Override
public boolean equals(Object o) {
// ๊ฐ์ฒด ๋๋ฑ์ฑ์ ๋น๊ตํ๋ ๋ฉ์๋, 'id' ํ๋ ๊ธฐ์ค์ผ๋ก ๋น๊ต
if (this == o) return true;
if (!(o instanceof Post that)) return false;
return this.getId() != null && this.getId().equals(that.getId());
}
@Override
public int hashCode() {
return Objects.hash(this.getId());
}
}
๐พPostCommentDto
import com.project.sns.domain.Post;
import com.project.sns.domain.PostComment;
import com.project.sns.domain.UserAccount;
import java.time.LocalDateTime;
public record PostCommentDto(
Long id,
Long postId,
UserAccountDto userAccountDto,
Long parentCommentId,
String content,
LocalDateTime createdAt,
String createdBy,
LocalDateTime modifiedAt,
String modifiedBy
) {
public static PostCommentDto of(Long postId, UserAccountDto userAccountDto, String content) {
return PostCommentDto.of(postId, userAccountDto, null, content);
}
public static PostCommentDto of(Long postId, UserAccountDto userAccountDto, Long parentCommentId, String content) {
return PostCommentDto.of(postId, userAccountDto, parentCommentId, content, null, null, null, null);
}
public static PostCommentDto of(Long postId, UserAccountDto userAccountDto, Long parentCommentId, String content, LocalDateTime createdAt, String createdBy, LocalDateTime modifiedAt, String modifiedBy) {
return PostCommentDto.of(null, postId, userAccountDto, parentCommentId, content, createdAt, createdBy, modifiedAt, modifiedBy);
}
public static PostCommentDto of(Long id, Long postId, UserAccountDto userAccountDto, Long parentCommentId, String content, LocalDateTime createdAt, String createdBy, LocalDateTime modifiedAt, String modifiedBy) {
return new PostCommentDto(id, postId, userAccountDto, parentCommentId, content, createdAt, createdBy, modifiedAt, modifiedBy);
}
public static PostCommentDto fromEntity(PostComment entity) {
return new PostCommentDto(
entity.getId(),
entity.getPost().getId(),
UserAccountDto.fromEntity(entity.getUserAccount()),
entity.getParentCommentId(),
entity.getContent(),
entity.getCreatedAt(),
entity.getCreatedBy(),
entity.getModifiedAt(),
entity.getModifiedBy()
);
}
public PostComment toEntity(Post post, UserAccount userAccount) {
return PostComment.of(
post,
userAccount,
content
);
}
}
๐พPostWithLikesAndHashtagAndCommentsResponse
import com.project.sns.dto.PostCommentDto;
import com.project.sns.dto.PostWithLikesAndHashtagsAndCommentsDto;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Collectors;
public record PostWithLikesAndHashtagAndCommentsResponse(
PostResponse postResponse,
String userId,
boolean isLike,
Set<String> likeUserId,
Set<PostCommentResponse> postCommentResponse,
Set<String> hashtags
) {
public static PostWithLikesAndHashtagAndCommentsResponse of(PostResponse postResponse, String userId, boolean isLike, Set<String> likeUserId, Set<PostCommentResponse> postCommentResponses, Set<String> hashtags) {
return new PostWithLikesAndHashtagAndCommentsResponse(postResponse, userId, isLike, likeUserId, postCommentResponses, hashtags);
}
public static PostWithLikesAndHashtagAndCommentsResponse fromDto(PostWithLikesAndHashtagsAndCommentsDto dto, String userId) {
Set<String> likes = dto.likeDtos().stream().map(likeDto -> likeDto.userId()).collect(Collectors.toSet());
Set<String> hashtags = dto.hashtagDtos().stream().map(hashtagDto -> hashtagDto.hashtagName()).collect(Collectors.toSet());
return new PostWithLikesAndHashtagAndCommentsResponse(
PostResponse.fromDto(dto.postDto()),
dto.userAccountDto().userId(),
null != userId && likes.contains(userId),
likes,
**organizeChildComments(dto.postCommentDtos()), // โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ**
hashtags
);
}
**// โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ**
private static Set<PostCommentResponse> organizeChildComments(Set<PostCommentDto> dtos) {
Map<Long, PostCommentResponse> map = dtos.stream()
.map(PostCommentResponse::fromDto)
.collect(Collectors.toMap(PostCommentResponse::id, Function.identity()));
map.values().stream()
.filter(comment -> comment.parentCommentId() != null)
.forEach(comment -> {
PostCommentResponse parentComment = map.get(comment.parentCommentId());
parentComment.childComments().add(comment);
});
return map.values().stream()
.filter(comment -> comment.parentCommentId() == null)
.collect(Collectors.toCollection(() ->
new TreeSet<>(Comparator
.comparing(PostCommentResponse::createdAt)
.reversed()
.thenComparingLong(PostCommentResponse::id)
)
));
}
}
๐พPostCommentResponse
import com.project.sns.dto.PostCommentDto;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public record PostCommentResponse(
Long id,
String content,
LocalDateTime createdAt,
String email,
String nickname,
String userId,
Long parentCommentId,
Set<PostCommentResponse> childComments
) {
// of: ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋, ๊ฐ์ฒด ์์ฑ๊ณผ ์ด๊ธฐํ๋ฅผ ๋์์ ์ฒ๋ฆฌํ์ฌ ์๋ก์ด PostCommentResponse ๊ฐ์ฒด๋ฅผ ๋ฐํ
public static PostCommentResponse of(Long id, String content, LocalDateTime createdAt, String email, String nickname, String userId) {
return PostCommentResponse.of(id, content, createdAt, email, nickname, userId, null);
}
public static PostCommentResponse of(Long id, String content, LocalDateTime createdAt, String email, String nickname, String userId, Long parentCommentId) {
Comparator<PostCommentResponse> childCommentComparator = Comparator
.comparing(PostCommentResponse::createdAt) // ์์ฑ์๊ฐ ์ค๋ฆ์ฐจ์
.thenComparingLong(PostCommentResponse::id); // ์์ฑ์๊ฐ ๋์ผํ ๋ ์ ๋ ฌ ๊ธฐ์ค
return new PostCommentResponse(id, content, createdAt, email, nickname, userId, parentCommentId, new TreeSet<>(childCommentComparator));
}
public static PostCommentResponse fromDto(PostCommentDto dto) {
return PostCommentResponse.of(
dto.id(), // ๋๊ธ ID
dto.content(),
dto.createdAt(),
dto.userAccountDto().email(),
dto.userAccountDto().nickname(),
dto.userAccountDto().userId(),
dto.parentCommentId()
);
}
}
'Back-end > Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [ํ๋ก์ ํธ] Redis๋ก ์บ์ฑ ๊ตฌํํ๊ธฐ (1) | 2024.09.18 |
|---|---|
| [ํ๋ก์ ํธ] N+1 ๋ฌธ์ ํด๊ฒฐํ๊ธฐ (Fetch Join+QueryDSL, Hibernate์ต์ ํ) (1) | 2024.09.12 |
| [ํ๋ก์ ํธ] JPQL โ QueryDSL ๋ก ๋ณ๊ฒฝํ๊ธฐ (0) | 2024.09.12 |
| [ํ๋ก์ ํธ] OAuth2 ์ธ์ฆ ๊ตฌํํ๊ธฐ (KakaoOAuth) + SecurityConfig ์ค์ (0) | 2024.09.10 |
| [ํ๋ก์ ํธ] N:N ๊ด๊ณ โ 1:N & N:1 ๋ก ๊ตฌํํ๊ธฐ (1) | 2024.09.09 |