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