인프런 커뮤니티 질문&답변

hun님의 프로필 이미지
hun

작성한 질문수

자바 ORM 표준 JPA 프로그래밍 - 기본편

orphanRemoval과 cascade의 관계

작성

·

1.6K

6

안녕하세요 영한님

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

다름이 아니라 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

12

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. hun님

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

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

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

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

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

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

감사합니다.

"추가로 실무에서 데이터를 향후 복구나 이력 확인을 위해서, 직접적으로 삭제하는 경우도 드물기 때문에 orphanRemoval 옵션 자체를 사용하는 경우도 드뭅니다." 는 중요한 실무 데이터만 해당하는 걸까요?
예를 들어 게시글 삭제, 댓글 삭제 같은 경우는 회원의 댓글,게시글 리스트에 orphanRemoval = true를 적용해서 remove() 메소드를 통해 삭제해도 될까요?

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. jayjoy05님

이 부분은 선택입니다 🙂

게시글을 삭제할 때 정말로 게시글을 DB에서 삭제하고 하고 댓글도 삭제를 한다면 사용하셔도 무방합니다.

감사합니다.

/////////////////////////////////////////

1

hun님의 프로필 이미지
hun
질문자

답변 감사합니다!

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

0

김영한님의 프로필 이미지
김영한
지식공유자

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

좋은 하루 되세요.

hun님의 프로필 이미지
hun

작성한 질문수

질문하기