영속성 전이, 고아 제거 (Cascade, Orphan Removal)

duckbill413·2023년 1월 21일
0

Spring JPA

목록 보기
5/7
post-thumbnail

Cascade (영속성 전이)

영속성 전이를 통해 하나의 Entity가 생성, 업데이트 될 때 하위 Entity도 같이 생성, 업데이트 해주는 방법이다.

Cascade

  • PERSIST, REMOVE
    • 상위 엔티티가 영속 처리될 때 하위 엔티티들도 같이 영속 처리
  • MERGE, REFRESH, DETACH
    • 상위 엔티티의 상태가 변경될 때 하위 엔티티들도 같이 상태 변경
  • All
    • 상위 엔티티의 모든 상태 변경이 하위 엔티티에 적용

Spring Cascade Type

  • CascadeType.ALL
    모든 Cascade를 적용
  • CascadeType.PERSIST
    엔티티를 영속화할 때, 연관된 엔티티도 함께 유지
  • CascadeType.MERGE
    엔티티 상태를 병합(Merge)할 때, 연관된 엔티티도 모두 병합
  • CascadeType.REMOVE
    엔티티를 제거할 때, 연관된 엔티티도 모두 제거
  • CascadeType.DETACH
    부모 엔티티를 detach() 수행하면, 연관 엔티티도 detach()상태가 되어 변경 사항 반영 X
  • CascadeType.REFRESH
    상위 엔티티를 새로고침(Refresh)할 때, 연관된 엔티티도 모두 새로고침

Orphan Removal

연관관계가 없어진 Entity를 제거

Cascade remove VS Orphan Removal

Cascade remove

  • setOrders(null)를 호출하면 관련 Order 엔티티가 DB에서 자동으로 제거되지 않습니다.

Orphan Removal

  • setOrders(null)를 호출하면 관련 Order 엔티티가 DB에서 자동으로 제거 됩니다.

예제

기본 book, publisher 데이터

book : Book(super=BaseEntity(createdAt=2022-12-21T04:28:48.567497500, updatedAt=2022-12-21T04:28:49.084049600), id=1, name=JPA Book1)
book : Book(super=BaseEntity(createdAt=2022-12-21T04:28:48.733219300, updatedAt=2022-12-21T04:28:49.086049300), id=2, name=JPA Book2)
book : Book(super=BaseEntity(createdAt=2022-12-21T04:28:48.738221100, updatedAt=2022-12-21T04:28:49.086049300), id=3, name=JPA Book3)
book : Book(super=BaseEntity(createdAt=2022-12-21T04:28:48.744382600, updatedAt=2022-12-21T04:28:49.087054), id=4, name=JPA Book4)

publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:28:48.764383900, updatedAt=2022-12-21T04:28:48.764383900), id=1, name=Go Study)
publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:28:48.799388600, updatedAt=2022-12-21T04:28:48.799388600), id=2, name=Stop Study)

Book Id 1's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:28:48.764383900, updatedAt=2022-12-21T04:28:48.764383900), id=1, name=Go Study)
Book Id 2's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:28:48.764383900, updatedAt=2022-12-21T04:28:48.764383900), id=1, name=Go Study)
Book Id 3's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:28:48.799388600, updatedAt=2022-12-21T04:28:48.799388600), id=2, name=Stop Study)
Book Id 4's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:28:48.799388600, updatedAt=2022-12-21T04:28:48.799388600), id=2, name=Stop Study)
  • CascadeType.REMOVE
    • 부모 데이터 삭제 publisherRepository.deleteById(1L);
      book : Book(super=BaseEntity(createdAt=2022-12-21T04:44:35.479485, updatedAt=2022-12-21T04:44:35.479485), id=3, name=JPA Book3)
      book : Book(super=BaseEntity(createdAt=2022-12-21T04:44:35.482511, updatedAt=2022-12-21T04:44:35.609654), id=4, name=JPA Book4)
      
      publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:44:35.475482, updatedAt=2022-12-21T04:44:35.475482), id=2, name=Stop Study)
      
      Book Id 3's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:44:35.475482, updatedAt=2022-12-21T04:44:35.475482), id=2, name=Stop Study)
      Book Id 4's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:44:35.475482, updatedAt=2022-12-21T04:44:35.475482), id=2, name=Stop Study)
      • 삭제 명령으로 부모 엔티티가 삭제 되었다.
      • 영속성 전이를 통해 자식 엔티티까지 같이 삭제 되었다.
    • 부모 데이터에서 자식 엔티티 간의 연결 관계 삭제
      • 연결관계 삭제 코드

         Publisher publisher = publisherRepository.findById(1L).get();
         publisher.getBooks().remove(0);
         publisherRepository.save(publisher);
        book : Book(super=BaseEntity(createdAt=2022-12-21T04:50:22.094850, updatedAt=2022-12-21T04:50:22.358255), id=1, name=JPA Book1)
        book : Book(super=BaseEntity(createdAt=2022-12-21T04:50:22.113942, updatedAt=2022-12-21T04:50:22.359602), id=2, name=JPA Book2)
        book : Book(super=BaseEntity(createdAt=2022-12-21T04:50:22.125450, updatedAt=2022-12-21T04:50:22.360635), id=3, name=JPA Book3)
        book : Book(super=BaseEntity(createdAt=2022-12-21T04:50:22.132505, updatedAt=2022-12-21T04:50:22.360635), id=4, name=JPA Book4)
        
        publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:50:21.920055, updatedAt=2022-12-21T04:50:21.920055), id=1, name=Go Study)
        publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:50:22.119936, updatedAt=2022-12-21T04:50:22.119936), id=2, name=Stop Study)
        
        Book Id 1's publisher : null
        Book Id 2's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:50:21.920055, updatedAt=2022-12-21T04:50:21.920055), id=1, name=Go Study)
        Book Id 3's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:50:22.119936, updatedAt=2022-12-21T04:50:22.119936), id=2, name=Stop Study)
        Book Id 4's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:50:22.119936, updatedAt=2022-12-21T04:50:22.119936), id=2, name=Stop Study)
      • 연결 관계 삭제로 book 1의 연결관계는 삭제 되었다.

      • 연결 관계가 삭제 되어도 자식 Entity까지 삭제 되지는 않는다.

  • Orphan removal
    • 부모 데이터 삭제 CascadType.REMOVE 를 하였을때와 같은 결과를 가진다.
    • 부모 데이터에서 자식 엔티티 간의 연결 관계 삭제
      book : Book(super=BaseEntity(createdAt=2022-12-21T04:54:49.058277, updatedAt=2022-12-21T04:54:49.283524), id=2, name=JPA Book2)
      book : Book(super=BaseEntity(createdAt=2022-12-21T04:54:49.070049, updatedAt=2022-12-21T04:54:49.284522), id=3, name=JPA Book3)
      book : Book(super=BaseEntity(createdAt=2022-12-21T04:54:49.075048, updatedAt=2022-12-21T04:54:49.284522), id=4, name=JPA Book4)
      
      publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:54:48.859726, updatedAt=2022-12-21T04:54:48.859726), id=1, name=Go Study)
      publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:54:49.063654, updatedAt=2022-12-21T04:54:49.063654), id=2, name=Stop Study)
      
      Book Id 2's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:54:48.859726, updatedAt=2022-12-21T04:54:48.859726), id=1, name=Go Study)
      Book Id 3's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:54:49.063654, updatedAt=2022-12-21T04:54:49.063654), id=2, name=Stop Study)
      Book Id 4's publisher : Publisher(super=BaseEntity(createdAt=2022-12-21T04:54:49.063654, updatedAt=2022-12-21T04:54:49.063654), id=2, name=Stop Study)
      • 부모 엔티티와 자식 엔티티 간의 연결 관계를 끊게 되면 자식 엔티티는 부모 엔티티가 없는 고아 엔티티가 된다.
      • orphan removal 속성이 true 이면 고아 엔티티를 삭제 시켜준다.
      • 위에서도 고아 엔티티가된 Book 1이 삭제된 것을 확인 할 수 있다.

부모 Entity 삭제

  • CascadeType.REMOVEorphanRemoval=true 는 부모 엔티티를 삭제하면 자식 엔티티도 삭제한다.

부모 Entity에서 자식 Entity 제거

  • CascadeType.REMOVE 는 자식 엔티티가 그대로 남아있는 반면, orphanRemoval=true 는 자식 엔티티를 제거한다.

주의점

  • 두 케이스 모두 자식 엔티티에 딱 하나의 부모 엔티티가 연관되어 있는 경우에만 사용해야 한다.
  • 부모 엔티티를 제거하였을때 자식 엔티티까지 제거되는 불상사가 발생할 수 있다.

Software Delete

실제 운용환경에서 delete 방식은 위험성이 있으므로 자주 사용되는 방식은 아니다. 따라서, 소프트웨어적으로 delete 되었다로만 처리하는 경우가 많다.

따라서, DB에서는 값이 존재하지만 Logic 상에서는 값이 존재하지 않는 것으로 처리하는 것이다.

  • 예제
    @Entity
    @Where(clause = "deleted = false")
    public class Book {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
        private String category;
        private Long authorId;
        @ToString.Exclude
        private boolean deleted; // true : deleted, false : not deleted
    }
    1. Book entity에 대하여 deleted flag를 생성한다.
    2. Book entity에 @Where Annotation을 붙이고 deleted=false 옵션을 사용하면 JPA 쿼리문에 항상 where deleted = false 문이 붙게 된다.
profile
같이 공부합시다~

0개의 댓글