[Spring] Tree 구조의 데이터 삭제
by
이전에 Tree구조로 구성된 카테고리를 db를 구현하였다.
이 구조에서 delete기능을 수행시에 전체 데이터가 한번에 삭제되는 문제가 있었다.
- 도메인
public class MenuCategory {
...
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "parentId")
@JsonIgnore
private MenuCategory parent;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "parentId")
private List<MenuCategory> children = new ArrayList<>();
...
}
- import.sql을 통해 19개의 트리구조 데이터를 입력
INSERT INTO menu_category (name, parent_id, id) values ('categories', null , 19l)
INSERT INTO menu_category (name, parent_id, id) values ('밑반찬', 19l, 1l);
INSERT INTO menu_category (name, parent_id, id) values ('무침', 1l, 2l);
INSERT INTO menu_category (name, parent_id, id) values ('나물무침', 1l, 3l);
INSERT INTO menu_category (name, parent_id, id) values ('볶음', 1l, 4l);
INSERT INTO menu_category (name, parent_id, id) values ('조림', 1l, 5l);
INSERT INTO menu_category (name, parent_id, id) values ('국·찌개', 19l, 6l);
INSERT INTO menu_category (name, parent_id, id) values ('담백한국', 6l, 7l);
INSERT INTO menu_category (name, parent_id, id) values ('찌개', 6l, 8l);
...
테스트 파일
{
...
@Test
public void delete_operate_test() {
MenuCategory fstCategory = categoryRepository.findById(1l).get();
categoryRepository.delete(fstCategory);
log.info("남은 카테고리 : '{}'", categoryRepository.findAll());
assertThat(categoryRepository.findAll().size()).isEqualTo(18);
}
...
}
결과
설명
- 위와 같은식으로 delete쿼리가 연속적으로 발생해서 모든 데이터를 삭제한다. ( 직계자식 -> 형제 -> 부모 순으로 delete쿼리가 발생한다. )
- 삭제순서 확인을위해 level이 4인 자식의 자식을 입력해 보았더니 이 노드의 부모를 삭제하기 전에 자식을 먼저 삭제하고 부모를 삭제하는것을 확인하였다.
- 쿼리의 삭제순서는 현재 노드 기준으로 자식 -> 본인 -> 부모 순이다. 이때 자식(1)에게 또 자식(2)이 있다면, 그 자식(2)을 삭제하고 나서 자식(1)을 삭제 한다. 논리적으로 일관된 순서를 보여준다는 점에 감탄.
- 또한 본인 삭제후 부모를 삭제할때 부모의 자식(현재 노드 기준으로는 형제)이 더있다면 자식을 모두 삭제한 후에 부모노드를 삭제 한다.
Cascade vs Fetch
1. 첫번째 가정
현재 문제는 cascadeType.remove로 인해 연관된 엔티티를 삭제하는데, 이때 @ManyToOne칼럼의 default FetchType인 EAGER로 인해 가져와진 parent를 삭제함으로써 발생한 일이라고 판단된다.
결과
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "parentId")
@JsonIgnore
private MenuCategory parent;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "parentId")
private List<MenuCategory> children = new ArrayList<>();
잘못된 가정이었음을 알 수 있었다. 생각해보면 위의 가정은 sibling을 삭제하는 이유는 설명할 수 있지만, children을 삭제하는 이유는 설명 할 수 없다. 그렇다면 CascadType가 원인이라고 판단할 수 있다.
2. 두번째 가정
부모에게 설정된 Cascade.remove를 삭제하면 부모도 delete쿼리가 올라가는 일이 없을것이다.
결과
부모는 삭제하지 않고, 자식과 자식의 자식들만 삭제하는 모습을 부여준다
결론
부모 엔티티에 CascadeType 속성을 걸면 repository method가 부모를 타고 올라간다는 너무 당연한 점을 재학습하였다.
Subscribe via RSS