@Entity
@Table(name = "comments")
class Comment(
@field:Id
@field:GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
@field:ManyToOne(fetch = FetchType.LAZY)
@field:JoinColumn(name = "article_id")
val article: Article,
@field:ManyToOne(fetch = FetchType.LAZY)
@field:JoinColumn(name = "user_id")
val user: User,
@field:ManyToOne(fetch = FetchType.LAZY)
@field:JoinColumn(name = "parent_id")
val comment: Comment? = null,
@field:Lob
@field:Column(name = "content")
var content: String,
@field:Column(name = "is_deleted", nullable = false)
var isDeleted: Boolean = false,
@field:Column(name = "display_order", nullable = false)
var displayOrder: Int
)
2. 개요
문제가 될 수 있는 댓글을 완전히 지우지 않고 보관하기위해 프로젝트에서 댓글은 soft delete로 진행하기로 했다.
따라서 삭제 요청시 isDeleted flag를 변경하며 댓글과 관련된 게시글의 총 댓글개수는 감소시키는 방식으로 가져갔다.
그런데 클라이언트측에서 삭제된 댓글은 아예 정보를 클라이언트에게 안넘겨줬으면 한다고 요청했다.
따라서 우리가 전달해야할 댓글 종류를 정리해보면
1. 모든 부모 댓글 중 삭제되지 않은 부모 댓글
2. 모든 삭제된 부모댓글 중 대댓글이 하나도 없는 부모 댓글
3. 모든 삭제되지 않은 대댓글
3. 기존방식
no offset paging으로 마지막 댓글 id를 전달받고 이 후 댓글30개에 각각 대댓글 10개씩을 가져온다
이 때
1. 현재 게시글을 참조하는 모든 댓글 중 (현재 게시글의 부모댓글+대댓글 중)
2. 부모 댓글이라면 마지막 댓글 이후 30개
3. 자식 댓글이라면 부모 댓글의 displayOrder가 마지막 댓글 ~ 이후 30개에 속하고 자식 댓글 자체의 displayOrder는 처음 10까지인 애들
을 모두 한 번에 가져온다.
4. 이 후 (부모 댓글을 키 / 자식댓글을 위한 mutableList) map에 정보를 넣고 이를 돌면서 response를 만든다.
4. 문제
여기서 문제는 지금까지 우리가 isDeleted==true인 애들도 다 보내줬지만 이제는 아래만 선별하여 보내줘야한다는것
1. 모든 부모 댓글 중 삭제되지 않은 부모 댓글
2. 모든 삭제된 부모댓글 중 대댓글이 하나도 없는 부모 댓글
3. 모든 삭제되지 않은 대댓글
5. 고민
1. 완전삭제
- 기획을 따르기로 했고 soft delete를 유지하기로 했다.
2. 단순히 isDeleted == true면 안가져온다?
- 삭제된 부모댓글이어도 자식댓글이 하나라도 존재하면 "삭제된 댓글입니다"로 표현하기위해 전달해줘야한다.
3. 그럼 조건에 해당되는 부모댓글 다 가져온 후 대댓글을 추가로가져오면?
- 부모댓글 30개마다 한 번의 쿼리가 발생
목표 : 부모댓글을 다 가져온 후 이에 관련된 자식 댓글을 한 번에 가져오기.
6. 해결법
솔직히 팀원분들과 아무리 생각을해봐도 원하는 답을 찾을 수 없어서 기획 변경을 요청할까하다가 아래 글을 찾았따.
- https://stackoverflow.com/questions/2129693/using-limit-within-group-by-to-get-n-results-per-group
우선 조건에 부합하는 30개의 부모 댓글을 가져온 후 각각 자식 댓글을 가져올 때 아래 쿼리를 활용
적용한 네이티브 쿼리
WHERE parent_id IN :parents AND is_deleted = false에서 parents는 파라미터로 받은 조건에 부합한 부모 댓글 리스트
--
SELECT
c.*
FROM
comments AS c JOIN (
SELECT parent_id, GROUP_CONCAT(id) as recomments
FROM comments
WHERE parent_id IN :parents AND is_deleted = false
GROUP BY parent_id
) group_max
ON c.parent_id = group_max.parent_id
AND FIND_IN_SET(id, recomments) BETWEEN 1 AND 10
ORDER BY c.parent_id AND c.id;
7. 분석
1. 자식댓글을 부모 댓글(부모 댓글id)별로 그룹화
- 그럼 이전에 먼저 구한 조건에 부합하는 30개의 부모댓글별로 자식 댓글을 가져오는데
- ex )
부모 : 자식 댓글들
1 : 11,12,13,14,15..
2: 21,22,23,24,25...
...
30 : 301,302,303 ...
2. AND FIND_IN_SET(id, recomments) BETWEEN 1 AND 10
그 중에서도 각 자식들의 id로 find in set했을 때 1~10번 이내의 애들만
즉
- 먼저 조건에 부합하는 부모 댓글을 찾고 이를 매개변수로 넘겨준다
- 해당 부모댓글마다 삭제되지 않은 자식 댓글을 찾아온다
- 그 중에서도 앞에서부터 1~10번. 10개의 대댓글만 가져온다.
1. 기존 comment entity
2. 개요
3. 기존방식
4. 문제
5. 고민
6. 해결법
7. 분석