팀원들과 JPA에 대해 얘기를 나누던 도중, orphanRemoval
과 CascadeType.REMOVE
에 살펴보는 시간을 갖게 되었다.
두 개의 차이가 무엇인지 궁금하여 검색도 해보고 테스트도 진행하게 되었다.
그러나, 전혀 예상치 못한 상황을 겪게 되었다.
이러한 상황에 대해 공유하고자 한다.
특정 엔티티에서 연관된 엔티티도 함께 영속 상태로 만들거나 제거할 때 사용되는 기능이다.
즉, 부모 엔티티를 저장하거나 삭제할 때, 자식 엔티티도 저장하거나 삭제되도록 영속성을 전이시키는 것이다.
단, 부모 엔티티와 자식 엔티티의 연관 관계가 끊겼다고 해도, DB에는 영향이 가진 않는다.
cascade
옵션을 통해 설정이 가능하다.
@Entity
public class Board {
public Board() {
}
public Board(final Long id) {
this.id = id;
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "board", cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
}
부모 엔티티와 자식 엔티티의 연관 관계가 끊겼을 때, 해당 자식 엔티티는 부모 엔티티를 가지지 않는 고아 상태가 된다.
부모 엔티티가 삭제됐을 때도 자식 엔티티를 삭제하게 된다.(cascade=CascadeType.REMOVE
와 같음)
이러한 고아 엔티티를 DB에서 삭제시켜주는 기능을 의미한다.
orphanRemoval=true
옵션을 통해 설정이 가능하다.
@Entity
public class Board {
public Board() {
}
public Board(final Long id) {
this.id = id;
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "board", orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
}
orphanRemoval=true
기능을 테스트하기 위해 다음과 같은 테스트를 진행하게 되었다.
@Entity
public class Board {
public Board() {
}
public Board(final Long id) {
this.id = id;
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "board", orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
}
@Test
@Rollback(value = false)
void setup(){
boardRepository.deleteAll();
commentRepository.deleteAll();
Board board = boardRepository.save(new Board(1L));
commentRepository.save(new Comment(1L, board));
commentRepository.save(new Comment(2L, board));
commentRepository.save(new Comment(3L, board));
}
@Test
@Rollback(value = false)
@Transactional
public void test(){
Board board = boardRepository.findById(1L).get();
board.getComments().remove(0);
}
그러나 결과는 DB에 comment가 삭제되지 않았다.
그러나 orphanRemoval=true
와 cascade=CascadeType.PERSIST
를 같이 적용해서 테스트를 진행해보니 DB에 comment가 하나 삭제된 모습을 볼 수 있었다.
이러한 상황에 대해 검색을 진행해본 결과, JPA의 구현체인 Hibernate에서의 버그인 것으로 나타났다.
Hibernate의 이슈 목록에도 해당 게시글과 관련된 내용이 존재한다.
https://hibernate.atlassian.net/browse/HHH-6709
orphanRemoval=true
를 사용하려면, cascade=CascadeType.PERSIST
와 함께 사용해야 한다.
https://www.inflearn.com/questions/137740/orphanremoval%EA%B3%BC-cascade%EC%9D%98-%EA%B4%80%EA%B3%84
https://hibernate.atlassian.net/browse/HHH-6709