• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

orphanRemoval과 cascade의 관계

21.01.23 20:27 작성 조회수 817

4

안녕하세요 영한님

강의 정말 잘 듣고 있습니다!

다름이 아니라 orphanRemoval과 cascade의 명확한 차이를 공부하던 도중 이해가 가지 않는 것이 있어서 질문 드립니다!

환경은 Spring Boot 2.4.2 입니다.

cascade는 엔티티의 상태변화를 전파하는 것이며, orphanRemoval는 연관관계가 끊어진 Entity는 자동으로 삭제하는 것으로 알고 있습니다.

근데 부모에서 orphanRemoval를 true로 설정 후, 컬렉션에서 자식 Entity를 삭제해도 Delete 쿼리가 아닌 update 쿼리가 발생했습니다.

그래서 여러가지 테스트 해보니 CascadeType.ALL 아니면 CascadeType.PERSIST시 orphanRemoval 이 정상적으로 동작하였습니다.

아래 코드에서 Board Entity에서 cascade 옵션을 지우면 update 쿼리가 발생하여 테스트는 실패하며, cascade 옵션을 ALL 또는 PERSIST을 사용하면 delete 쿼리가 발생하여 테스트가 정상적으로 통과합니다.

책의 p.311을 보면 orphanRemoval만 사용한 예제가 있는데 하이버네이트 버전이 올라가면서 변경된 것일까요?

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Board {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String content;

    @OneToMany(mappedBy = "board", cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();

    @Builder
    public Board(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public Board addComment(Comment comment){
        this.comments.add(comment);
        comment.setBoard(this);
        return this;
    }

    public Board removeComment(Comment comment){
        this.comments.remove(comment);
        comment.setBoard(null);
        return this;
    }
}

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Comment {

    @Id
    @GeneratedValue
    private Long id;

    private String comment;

    @ManyToOne
    @Setter
    private Board board;

    @Builder
    public Comment(String comment) {
        this.comment = comment;
    }

}

@DataJpaTest(properties = "classpath:application-test.yml")
class BoardTest {

    @Autowired
    BoardRepository boardRepository;

    @Autowired
    CommentRepository commentRepository;

    @DisplayName("orphanRemoval 테스트")
    @Test
    @Rollback(false)
    public void orphanRemovalTest(){

        Board board = Board.builder().title("1번글").content("1번글 컨텐츠").build();
        boardRepository.save(board);

        Comment comment = Comment.builder().comment("1번글 댓글").build();

        board.addComment(comment);
        commentRepository.save(comment);

        entityManager.flush();
        entityManager.clear();

        Board board1 = boardRepository.findById(board.getId()).get();
        board1.removeComment(board1.getComments().get(0));

        List<Comment> commentAll = commentRepository.findAll();
        assertThat(commentAll.size(), is(0));
    }

}

답변 3

·

답변을 작성해보세요.

10

안녕하세요. hun님

해당 부분은 JPA 스펙상 원칙적으로 CascadeType.PERSIST이 없어도 orphanRemoval만으로 삭제되어야 하는 것이 맞습니다.

하이버네이트 구현체에서는 해당 기능에 버그가 있고, 그래서 CascadeType.PERSIST(또는 ALL)이 함께 적용되어야 동작합니다.

(찾아보니 이클립스 링크 같은 다른 구현체에서는 정상 동작한다는 이야기도 있네요.)

버그 리포트: https://hibernate.atlassian.net/browse/HHH-6709

그런데 이 부분이 실제 개발을 할 때는 크게 영향이 없는데, 그 이유는 orphanRemoval만 따로 적용하는 경우는 거의 없고, 보통 주인 엔티티가 하위 엔티티를 관리하는 경우에는 CascadeType.PERSIST + orphanRemoval을 함께 적용하기 때문입니다.

추가로 실무에서 데이터를 향후 복구나 이력 확인을 위해서, 직접적으로 삭제하는 경우도 드물기 때문에 orphanRemoval 옵션 자체를 사용하는 경우도 드뭅니다.

감사합니다.

1

hun님의 프로필

hun

질문자

2021.01.24

답변 감사합니다!

추가로 어떻게 사용하는 것이 좋은 것인지 말씀해 주셔서 정말 감사합니다!!

0

hun님 질문을 올리기 전에 먼저 다양하게 테스트 해보시고, 또 질문도 잘 정리해서 올려주셔서 감사합니다.

좋은 하루 되세요.