Cascade.All 연관관계 Delete, ConcurrentModificationException remove 문제

이효곤·2023년 4월 6일

트러블슈팅

목록 보기
3/7


@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
    private List<Comment> comments = new ArrayList<>();
    
@ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

다음과 같은 양방향 관계를 가진 객체 Post와 comment가 있을때,
Post를 삭제할 때 Post가 가진 comment들을 동시 삭제하는 테스트 코드에서 comment가 삭제 되지 않는 문제가 발견되었습니다.

삭제가 되지 않는 이유

기존 삭제가 되지 않는 코드는 다음과 같습니다.

     Post post = postRepository.findById(postId).orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."));
        post.getComments().forEach(comment -> {
            commentRepository.delete(comment);
        });
        postRepository.delete(post);

위 코드의 문제는 cascade 때문에 객체 연관관계가 남아있으므로 (post에서 comment를 List로 가지고있음) 삭제가 불가능합니다.
그러므로 comment를 삭제하고 싶으면 Post가 가지고 있는 comment의 리스트를 모두 접근하여 삭제해주어야 합니다.

Cascade.All을 사용하는 이유는 (정확히는 remove)인데 이렇게 리스트의 모든 요소를 순회하며 삭제하는 것은 Cascade.All을 굳이 사용할 의미가 없다고 생각되었습니다. 그러므로 Cascade.All 옵션을 삭제하고 코드를 다음과 같이 변경하였습니다.

 Post post = postRepository.findById(postId).orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."));
        post.getComments().forEach(comment -> {
            post.getComments().remove(comment);
            commentRepository.delete(comment);
        });
        postRepository.delete(post);

연관관계를 삭제하는 코드를 추가 후 테스트를 실행 시 comment가 2개 등록되었을때 1개만 삭제되는 의도치 않는 결과가 나왔습니다.

ConcurrentModificationException : 순회하면서 List를 동시에 변경하고 있다.

ConcurrentModificationException 이 발생하는 이유는 for-each 구문으로 post.getComments()를 순회하면서 해당 List를 동시에 변경하고 있기 때문입니다. 이럴 경우에는 Iterator 를 사용하여 동시 수정을 방지해야합니다.

Iterator<Comment> iterator = post.getComments().iterator();
while (iterator.hasNext()) {
    Comment comment = iterator.next();
    commentRepository.delete(comment);
    iterator.remove();
}

Iterator는 순차적인 데이터 접근을 할 때 사용하는 객체로, next() 메소드로 다음 요소를 가져오면서 현재 요소를 가리키는 커서를 이동시킵니다.

remove() 메소드는 커서가 가리키는 요소를 삭제하며, 동시 수정을 방지하기 위해 ConcurrentModificationException을 발생시킵니다.

따라서 Iterator를 사용하면 순차적인 데이터 접근과 컬렉션의 동시 수정을 안전하게 수행할 수 있습니다.

자바 공식 문서를 보면 이 메소드를 사용하는 것이 컬렉션을 순회하면서 원소를 삭제할 수 있는 유일하게 안전한 방법이라고 가이드하고 있습니다.

profile
develop

0개의 댓글