일기: 오늘은 옥수수랑 팝콘을 먹었다.
└─ 댓글 1: 둘 중에 뭐가 더 맛있나요?
└─ 답글 1-1: 콘치즈요.
└─ 답글 1-1-1: ^^;
└─ 댓글 2: 카라멜 팝콘 짱
어떻게 구현했나?
Adjacency List(인접 리스트)를 사용해서 구현.
부모 댓글을 가리키는 필드: 각 댓글이 부모 댓글을 참조하는 parentComment 필드를 통해 계층 구조를 형성
자식 댓글을 저장하는 필드: 부모 댓글이 여러 자식 댓글을 가질 수 있도록 childComments 리스트를 정의
댓글 entity 구성
id : primary key
diary_id : 일기 id
member_id : 댓글 작성자
content : 댓글 내용
parentComment : 부모 댓글
childComments : 자식 댓글 리스트
deleted : 삭제 여부 (true/false)
created_at : 댓글 생성 시간
updated_at : 댓글 수정 시간
댓글 저장
@Transactional
public CommentResponseDTO save(Long diaryId, CommentSaveRequestDTO requestDTO, Long logInMemberId) {
Member member = getMemberById(logInMemberId);
Diary diary = getDiaryById(diaryId);
Comment parentComment = getParentCommentById(requestDTO.parentCommentId());
Comment comment = requestDTO.toEntity(member, diary, parentComment);
commentDAO.save(comment);
return CommentResponseDTO.from(comment);
}
로그인 된 사용자의 정보를 바탕으로 댓글을 생성하고, 댓글이 달린 일기와 부모 댓글 정보를 포함하여 최종적으로 데이터베이스에 저장된 댓글 정보를 클라이언트에게 반환
댓글 수정
@Transactional
public CommentResponseDTO update(Long CommentId, CommentUpdateRequestDTO requestDTO, Long logInMemberId) {
Member member = getMemberById(logInMemberId);
Comment comment = getCommentById(CommentId);
validateCommentAuthor(member, comment);
comment.update(requestDTO.content());
return CommentResponseDTO.from(comment);
}
로그인된 사용자의 정보를 바탕으로 수정하려는 댓글을 조회하고, 댓글 작성자인지 확인한 후 댓글 내용을 업데이트하여 최종적으로 클라이언트에게 반환
댓글 삭제
@Transactional
public CommentDeleteResponseDTO delete(Long CommentId, Long logInMemberId) {
Member member = getMemberById(logInMemberId);
Comment comment = getCommentById(CommentId);
validateCommentAuthor(member, comment);
validateNotDeleted(comment);
comment.delete();
return CommentDeleteResponseDTO.from(comment);
}
로그인된 사용자의 정보를 바탕으로 삭제하려는 댓글을 조회하고, 댓글 작성자인지 확인한 후 댓글을 삭제 상태로 변경하여 최종적으로 클라이언트에게 반환
일기: 오늘은 옥수수랑 팝콘을 먹었다.
└─ 댓글 1: 둘 중에 뭐가 더 맛있나요?
└─ 답글 1-1: **삭제된 댓글입니다.**
└─ 답글 1-1-1: ^^;
└─ 댓글 2: 카라멜 팝콘 짱
댓글이 삭제 되어도 해당 댓글에 대한 대댓글을 유지하기 위해, 실질적으로 삭제하지 않고 deleted라는 삭제 여부를 저장하는 컬럼을 둠.
public record CommentResponseDTO(
Long memberId,
String content,
boolean deleted,
String createdDate,
String createdAt,
List<CommentResponseDTO> childComments
) {
public static CommentResponseDTO from(Comment comment) {
return new CommentResponseDTO(
comment.getMember().getId(),
comment.getContent(),
comment.isDeleted(),
comment.getCreatedAt().format(DateTimeFormatter.ofPattern("yy년 MM월 dd일")),
comment.getCreatedAt().format(DateTimeFormatter.ofPattern("HH:mm")),
comment.getChildComments().stream().map(CommentResponseDTO::from).collect(Collectors.toList())
);
}
public static CommentResponseDTO fromDeleted(Comment comment) {
return new CommentResponseDTO(
null,
null,
true,
null,
null,
comment.getChildComments().stream().map(CommentResponseDTO::from).collect(Collectors.toList())
);
}
}
CommentResponseDTO에 fromDeleted 메서드를 추가하여 삭제된 상태일 때는 댓글에 대한 정보를 null값을 받아 반환하도록 함.
댓글 불러오기
@Transactional(readOnly = true)
public List<CommentResponseDTO> findAll(Pageable pageable, Long diaryId) {
Diary diary = getDiaryById(diaryId);
List<Comment> comments = commentDAO.findAllByDiaryId(diaryId, pageable);
List<Comment> hierarchicalComments = convertHierarchy(comments);
return hierarchicalComments.stream()
.map(this::mapToDTO)
.collect(Collectors.toList());
}
private List<Comment> convertHierarchy(List<Comment> comments) {
Map<Long, Comment> map = new HashMap<>();
List<Comment> parentComments = new ArrayList<>();
for (Comment comment : comments) {
if (comment.getParentComment() != null) {
// 부모 댓글이 있는 경우
Comment parentComment = map.get(comment.getParentComment().getId());
if (parentComment != null) {
parentComment.getChildComments().add(comment);
}
} else {
// 부모 댓글이 없는 경우
parentComments.add(comment);
}
map.put(comment.getId(), comment);
}
return parentComments;
}
private CommentResponseDTO mapToDTO(Comment comment) {
if (comment.isDeleted()) {
return CommentResponseDTO.fromDeleted(comment);
}
return CommentResponseDTO.from(comment);
}
findAllByDiaryId로 db에서 부모 댓글이 null인 최상위 댓글을 먼저 오도록 정렬해서 데이터 가져옴
convertHierarchy를 이용하여 평면 구조로 조회된 댓글 리스트를 계층형 구조로 변환
주요 기능
계층형 댓글 구조란?
댓글 시스템에서 댓글과 대댓글이 계층적으로 연결되어 있는 형태를 의미
어떻게 구현했나?
Adjacency List(인접 리스트)를 사용해서 구현.
댓글 저장
로그인 된 사용자의 정보를 바탕으로 댓글을 생성하고, 댓글이 달린 일기와 부모 댓글 정보를 포함하여 최종적으로 데이터베이스에 저장된 댓글 정보를 클라이언트에게 반환
댓글 수정
로그인된 사용자의 정보를 바탕으로 수정하려는 댓글을 조회하고, 댓글 작성자인지 확인한 후 댓글 내용을 업데이트하여 최종적으로 클라이언트에게 반환
댓글 삭제
로그인된 사용자의 정보를 바탕으로 삭제하려는 댓글을 조회하고, 댓글 작성자인지 확인한 후 댓글을 삭제 상태로 변경하여 최종적으로 클라이언트에게 반환
댓글이 삭제 되어도 해당 댓글에 대한 대댓글을 유지하기 위해, 실질적으로 삭제하지 않고 deleted라는 삭제 여부를 저장하는 컬럼을 둠.
CommentResponseDTO에 fromDeleted 메서드를 추가하여 삭제된 상태일 때는 댓글에 대한 정보를 null값을 받아 반환하도록 함.
댓글 불러오기