CascadeType.REMOVE vs @OnDelete(action = OnDeleteAction.CASCADE)

2023. 7. 24. 14:50·🥑 Web Technoloy

  • 프로젝트를 진행하다가 연관관계가 있는 엔티티를 연쇄적으로 제거하기 위해 사용되는 두 가지 방식에 대해 알게되었고, 여기서 새롭게 알게 된 내용을 간단히 정리해보려고 한다.
주관적인 내용이 많이 포함되어 있을 수 있음

💡 CascadeType.REMOVE와 @OnDelete(action = OnDeleteAction.CASCADE)

  • 둘의 가장 큰 차이점은 JPA에 의해 처리되느냐, DDL에 의해 DB 단에서 처리되느냐이다.
  • 전자의 방식을 취할 경우, JPA에 의해 외래 키를 찾아가며 참조하는 레코드를 제거해주게 된다.
  • 따라서, JPA 상에서는 참조하고 있는 레코드의 개수만큼 DELETE 쿼리가 생성된다.
  • 후자의 방식을 취할 경우, 데이터베이스 자체에서 on delete cascade 제약조건이 걸리게 된다. 이를 통해 참조하는 레코드가 모두 제거되는 것이다.
  • 따라서, JPA 상에서는 한 개의 DELETE 쿼리가 생성되고, 데이터베이스에서 이를 처리해준다.
  • 간단한 테스트로 확인해보자.
@Test
void deleteCascadeTest() {
    // given
    Category category1 = categoryRepository.save(createCategoryWithName("category1"));
    Category category2 = categoryRepository.save(createCategory("category2", category1));
    Category category3 = categoryRepository.save(createCategory("category3", category2));
    Category category4 = categoryRepository.save(createCategoryWithName("category4"));
    clear();

    // when
    categoryRepository.deleteById(category1.getId());
    clear();

    // then
    List<Category> result = categoryRepository.findAll();
    assertThat(result.size()).isEqualTo(1);
    assertThat(result.get(0).getId()).isEqualTo(category4.getId());
}
  • 우리는 위 테스트를 수행하면서 각 방식마다 쿼리가 어떻게 수행되는지 살펴볼 것이다.
  • 1번 카테고리를 제거하면, 그의 모든 하위 카테고리인 2번과 3번 카테고리도 연쇄적으로 제거되어야 한다.
  • 최종 결과는 1개의 카테고리만 조회될 것이다.
  • 위에서 작성했던 Category 엔티티를, 다음과 같이 설정하여 테스트를 수행해보자.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Category parent;

@OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private List<Category> children;
  • 테스트는 성공하였고, 쿼리 결과는 다음과 같다.
Hibernate: create table category (category_id bigint generated by default as identity, name varchar(30) not null, parent_id bigint, primary key (category_id))
...
Hibernate: alter table category add constraint FK2y94svpmqttx80mshyny85wqr foreign key (parent_id) references category
...

Hibernate: delete from category where category_id=?
Hibernate: delete from category where category_id=?
Hibernate: delete from category where category_id=?
  • category 테이블에 외래 키 제약 조건 외에는 별다른 제약 조건이 추가되지 않았다.
  • 상위 카테고리 한 개를 제거하기 위한 DELETE 쿼리와, 그의 하위 카테고리를 제거하기 위해 2개의 DELETE 쿼리가 연쇄적으로 수행된 것을 확인할 수 있다.
  • 이번에는 Category 엔티티를 다음과 같이 설정하여 테스트를 수행해보겠다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
@OnDelete(action = OnDeleteAction.CASCADE)
private Category parent;
  • @OneToMany에 설정했던 cascade=CascadeType.REMOVE는 제거하고, @ManyToOne에 @OnDelete(action = onDeleteAction.CASCADE)를 설정해주었다.
  • 테스트는 성공하였고, 쿼리 결과는 다음과 같다.
Hibernate: create table category (category_id bigint generated by default as identity, name varchar(30) not null, parent_id bigint, primary key (category_id))
...
Hibernate: alter table category add constraint FK2y94svpmqttx80mshyny85wqr foreign key (parent_id) references category on delete cascade
...
Hibernate: delete from category where category_id=?
  • alter 문의 뒷 부분을 자세히 보면, on delete cascade가 추가되어 있는 것을 확인할 수 있다.
  • 즉, DDL에 의해 스키마 자체에 해당 제약 조건이 추가된 것이다.
  • 또한, delete 쿼리는 한 건만 생성되었지만, 테스트는 성공하였다.
  • 즉, JPA가 아닌, DDL로 제약 조건을 설정해둔 데이터베이스에서 참조하는 레코드들을 연쇄적으로 제거해준 것이다.

💡 둘 중 무엇을 선택해야 할까?

  • 그래서 이 두 방식 중 어떤 방식을 언제 사용해야 할까?

 

성능 상의 차이

  • 일단 필자가 현재 진행중인 프로젝트에서 @OnDelete를 사용한 이유는 DELETE 쿼리를 줄이기 위해서이다.
  • @OnDelete 방식에서 JPA는 단일한 DELETE 쿼리만 전송하여 참조하는 레코드들을 연쇄적으로 제거해준다.
  • 하지만 CascadeType.REMOVE 방식은 JPA에서 외래 키를 통해 참조하는 레코드들을 제거하기 위해 그 개수만큼의 DELETE 쿼리를 전송해야 한다.
  • 이러한 까닭에 쿼리를 1개만 생성하는 @OnDelete 방식이 성능적으로 이점이 있지 않을까 싶다.

 

운영 상의 차이

  • @OnDelete 방식으로 테이블을 생성하여 데이터베이스를 직접 다루다보면, on delete cascade에 의해 어떠한 레코드의 참조 레코드까지 연쇄적으로 삭제해버리는 실수 할 여지가 있는 반면,
  • CascadeType.REMOVE 방식으로 데이터베이스를 다루다보면, 의존성이 있는 레코드를 제거하기 위해서는 JPA에서 프로그램적으로만 처리할 수 있다보니, 운영자가 의존성이 있는 레코드를 직접 삭제하게 되는 실수의 여지가 비교적 줄어들 수 있다.
  • 하지만 반대로 생각해보면, 스키마 구조를 변경하는 등 데이터베이스를 조작하는데 번거로움이 생길 수도 있을 것 같다.

 

코드 상의 차이

  • Team과 Member라는 1:N 관계의 엔티티가 있다고 가정해보자.
  • Member는 하나의 Team에 속할 수 있고, Team에는 여러 명의 Member가 소속될 수 있다.
  • 연관관계의 주인은, Team의 primary key를 외래 키로 가지고 있는 Member가 될 것이다.
  • @OnDelete 방식을 취하면, Member에서 @ManyToOne으로 선언한 Team 필드에 단순히 해당 어노테이션을 지정해주기만 하면 된다.
  • 물론 CascadeType.REMOVE 방식을 취해도, Team에서 @OneToMany으로 선언한 Member 필드에 해당 어노테이션을 지정해주기만 하면 된다는 것은 동일하다.
  • 하지만 @OneToMany는 JPA로 인하여 만들어지는 양방향 의존 관계이다.
  • 실제 연관 관계의 주인은 Member이기 때문에, Team에서 반드시 Member를 의존하고 있을 이유가 없다.
  • 즉, JPA를 이용한 양방향 의존 관계를 만들어줘야할 특별한 이유가 없음에도, 단지 연쇄적으로 삭제하기 위해서 새로운 의존 관계를 만들어줘야하는 것이다.
  • 양방향 의존 관계를 무분별하게 설정하면, 언제 어떠한 문제가 생길지 모른다.
  • 예를 들어, 롬복으로 @ToString이나 @EqualsAndHashCode 등을 사용하는 경우 서로를 계속 참조하며 스택오버플로우가 발생할 수도 있다.
  • 새로운 의존 관계 생성을 방지하고, 그로 인해 발생할 수 있는 문제를 미연에 방지할 수 있다는 점에서는 @OnDelete 방식이 우위를 점한다고 생각한다.
  • 물론, 이것도 상황에 따라 다를 수 있다.

📌 Reference

  • https://kukekyakya.tistory.com/546
저작자표시 (새창열림)

'🥑 Web Technoloy' 카테고리의 다른 글

@OneToMany 단방향  (0) 2023.07.18
Vue.js & Spring Boot 연동 및 개발환경 구축  (1) 2023.06.30
@RequestBody vs @ModelAttribute  (0) 2023.06.24
더티 체킹(Dirty Checking)이란?  (0) 2023.06.13
AJAX란?  (0) 2023.06.13
'🥑 Web Technoloy' 카테고리의 다른 글
  • @OneToMany 단방향
  • Vue.js & Spring Boot 연동 및 개발환경 구축
  • @RequestBody vs @ModelAttribute
  • 더티 체킹(Dirty Checking)이란?
Baeg-won
Baeg-won
  • Baeg-won
    좋았다면 추억이고 나빴다면 경험이다.
    Baeg-won
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 🍃 Spring, Spring Boot
        • 스프링 프레임워크 기초
        • 스프링 핵심 원리 - 기본편
        • 자바 ORM 표준 JPA 프로그래밍 - 기본편
        • 스프링 MVC
        • 실전! 스프링 부트와 JPA 활용1 - 웹 애플리..
      • 🥑 Web Technoloy
      • 🚗 Backend Toy Project
        • 스프링 부트 게시판
        • Photogram
        • Baeg-won Clothing Gallery
      • 🥇 Problem Solving
        • Breadth-First Search
        • Depth-First Search
        • Backtracking
        • Simulation
        • Two-pointer
        • Binary Search
        • Greedy
        • Dynamic Programming
        • Minimum Spanning Tree
        • Dijkstra
        • Floyd warshall
      • ☕ Java
        • 명품 자바 에센셜
        • Applications
      • 🍦 JavaScript
        • JavaScript 기초
      • 🐧 Linux
        • 이것이 리눅스다(CentOS 8)
      • 📟 Database
        • 혼자 공부하는 SQL
      • 🧬 Data Structure
      • 🎬 HTML
      • 🎤 Tech Interview
      • 📌 etc
        • Unity 2D Raising Jelly Game
        • C++
        • 영어 쉐도잉
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Baeg-won
CascadeType.REMOVE vs @OnDelete(action = OnDeleteAction.CASCADE)
상단으로

티스토리툴바