[Spring JPA] 대댓글 삭제, 자식 댓글 남아있는 오류 해결 과정

·2025년 7월 3일

troubleshooting

목록 보기
5/9
post-thumbnail

플랜줍줍 프로젝트 진행 중 대댓글 삭제할 경우 DB에 반영되지 않아 해결하는 과정을 담은 글입니다.

대댓글(계층형 댓글)을 구현한 상태입니다.

0계층(0 depth): 부모 댓글(최상위 댓글)
1계층(1 depth): 대댓글(부모 댓글에 달린 댓글)

문제 상황

@PathVariable을 이용해서 부모댓글 id를 기준으로
해당 부모의 자식 댓글을 조회하는 컨트롤러를 만들었다.

그런데 자식 댓글을 삭제해도,
다시 조회해보면 여전히 자식 댓글이 남아있는 문제가 발생했다.

처음에는 댓글자체는 삭제된 상태이고 부모 댓글에 반영이 안 되는 줄 알았다.

DB에도 자식 댓글이 그대로 남아있는 걸 확인할 수 있었다.

문제 코드

Comment Domain

// 생략
public class Comment extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long commentId;

    @ManyToOne
    private Comment parent;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Comment> children = new ArrayList<>();

   // 생략
}

CommentService.java

public CommentResDto deleteComment(Long commentId) {
        // 생략
        Comment findComment = commentRepository.findById(commentId)
                .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 댓글입니다."));

        if (!member.equals(findComment.getMember())) {
            throw new IllegalStateException("본인 댓글만 수정할 수 있습니다.");
        }

        commentRepository.delete(findComment);
        return CommentResDto.fromEntity(findComment);
}

단순히 JPA의 delete()만 호출하면
DB와 API 응답 모두에서 자식 댓글이 삭제될 줄 알았다.


시도해본 것들

CommentService.java

public CommentResDto deleteComment(Long commentId) {
        // 생략
        Comment findComment = commentRepository.findById(commentId)
                .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 댓글입니다."));

        if (!member.equals(findComment.getMember())) {
            throw new IllegalStateException("본인 댓글만 수정할 수 있습니다.");
        }

        commentRepository.deleteById(commentId);
        return CommentResDto.fromEntity(findComment);
}

혹시 deleteById로 해도 다를까 싶어 아래처럼도 수정해봤지만
역시 DB에 자식 댓글이 남아있는 현상은 해결되지 않았다.

deleteById vs delete

  • deleteById : id(PK) 값으로 해당 row를 삭제. 내부적으로 findById 후 delete 실행.
  • delete : 엔티티 객체 자체를 전달해 삭제. 영속성 컨텍스트에 엔티티가 있을 때 주로 사용.

문제가 발생한 이유

다시 코드를 꼼꼼히 살펴보니

JPA에서는 단순히 delete()만 호출한다고 해서
자식 엔티티가 항상 DB에서 자동 삭제되는 것은 아니라는 점을 명확히 인지하게 됐다.

부모의 children 컬렉션에서 직접 remove를 하지 않으면
JPA는 부모 컬렉션(List children)에서 직접 remove(혹은 clear)를 해줘야
해당 자식이 orphan(고아)로 인식되어 DB에서 삭제한다.
또한, orphanRemoval = true가 없는 경우 단순히 delete만 해서는 부모 컬렉션에서는 계속 남아 있을 수 있다.

또한 orphanRemoval 옵션이 빠져 있었다.

cascade = CascadeType.ALL만 있으면
부모 댓글 삭제 시 자식 댓글의 parent_id만 null로 바뀌고,
자식 댓글 자체는 DB에서 삭제되지 않는다.
orphanRemoval = true를 추가하면
부모의 컬렉션에서 빠진 자식이 DB에서도 완전히 삭제된다.


해결

(1) orphanRemoval = true 추가

@OneToMany(
  	mappedBy = "parent", 
  	cascade = CascadeType.ALL, 
  	orphanRemoval = true)
private List<Comment> children = new ArrayList<>();

(2) 부모의 children 컬렉션에서 직접 remove
실제 삭제 로직에서, 부모의 children 리스트에서 자식 댓글을 remove 해주면
JPA가 이를 orphan으로 인식해서 DB에서 삭제

// CommentService.java
public CommentResDto deleteComment(Long commentId) {
        // 생략
        Comment findComment = commentRepository.findById(commentId)
                .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 댓글입니다."));
       	// 생략
		
  		if (findComment.getParent() != null) {
  			// 자식 댓글인 경우 부모에게서 remove
            findComment.getParent().getChildren().remove(findComment);
        }

        commentRepository.deleteById(commentId);
        return CommentResDto.fromEntity(findComment);
}

삭제 API를 통해 DB에서 데이터가 잘 삭제되는 걸 확인할 수 있다.


정리

orphanRemoval = true는 꼭 필요하다!

부모의 children 컬렉션에서 직접 remove/clear 해줘야 JPA가 orphan을 감지해서 DB에서 삭제해준다.

단순히 delete()만 호출하면 원하는 동작이 나오지 않을 수 있으니,
JPA의 엔티티 상태와 연관관계 관리에 항상 신경쓰자.




0개의 댓글