본문 바로가기

개발/Jpa

[JPA] Cascade & OrphanRemoval

어떤 엔티티를영속 상태로 만들 때, 연관된 엔티티도 함께 영속화 하고 싶을때,

cascade 옵션을 통해 영속성 전이를 할 수 있다.

 

먼저 부모 엔티티를 영속화 할때 CascadeType 옵션을 주면 되는데, 아래의 6가지 옵션이 있다.

  • ALL
  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH

이 중 일반적으로 사용되는 ALL, PERSIST, REMOVE로 살펴본다.

하나의 Item 엔티티가 있고, 그 안에서 Option, OptionDetail 엔티티가 있는데,

Option이 부모 엔티티, OptionDetail 엔티티가 자식 엔티티이다.(편의상 Item 엔티티는 제외한다)


먼저 cascade를 설정하지 않은 경우를 보자.

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "OPTION")
public class Option {
    @Id
    @Column(name = "OPTION_ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long optionId;
    @Column(name = "NAME")
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ITEM_ID")
    private Item item;

    @Builder.Default
    @OneToMany(mappedBy = "option" ,fetch = FetchType.LAZY)
    private List<OptionDetail> optionDetails = new ArrayList<>();

    public void addOptionDetail(OptionDetail optionDetail) {
        optionDetails.add(optionDetail);
    }
}
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "OPTION_DETAIL")
public class OptionDetail {
    @Id
    @Column(name = "OPTION_DETAIL_ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long optionDetailId;
    @Column(name = "NAME")
    private String name;
    @Column(name = "PRICE")
    private int price;
    @Column(name = "STOCK_QUANTITY")
    private int stockQuantity;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "OPTION_ID")
    private Option option;
}

cascade 옵션이 없을 경우 테스트 코드를 통해 어떻게 쿼리가 실행되는지 로그로 확인해보자

    @Test
    @Transactional
    void cascade() {

        //given
        Option option = Option.builder().name("cascade_option").build();
        OptionDetail optionDetail = OptionDetail.builder().name("cascade_option_detail").price(100).stockQuantity(50).option(option).build();
        option.addOptionDetail(optionDetail);

        //when
        optionRepository.save(option);
        optionDetailRepository.save(optionDetail);
    }


이제 Option 엔티티에 cascade 옵션을 주고, ItemDetail은 따로 영속화하지 않는다.

    @Builder.Default
    @OneToMany(mappedBy = "option" ,fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    private List<OptionDetail> optionDetails = new ArrayList<>();
    @Test
    @Transactional
    void cascade() {

        //given
        Option option = Option.builder().name("cascade_option").build();
        OptionDetail optionDetail = OptionDetail.builder().name("cascade_option_detail").price(100).stockQuantity(50).option(option).build();
        option.addOptionDetail(optionDetail);

        //when
        optionRepository.save(option);
//        optionDetailRepository.save(optionDetail); //제거
    }

 

itemDetailRepository의 save()를 호출하지 않아도 option_detail 테이블에 insert 쿼리가 실행되는 것을 확인할 수 있다.


우리는 믿음이 부족하기 때문에, cascade 옵션을 제거한채로 동일한(주석 처리된) 테스트 코드를 실행해보자.

    @Builder.Default
    @OneToMany(mappedBy = "option" ,fetch = FetchType.LAZY) //cascade 제거
    private List<OptionDetail> optionDetails = new ArrayList<>();

option 테이블에 대한 insert 쿼리만 수행되고, option_detail은 수행되지 않는 것을 확인할 수 있다.


orphanRemoval 옵션은 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 "고아 객체"라고 하는데,

이러한 고아 객체를 자동으로 삭제해주는 기능을 한다.


먼저 cascade 옵션만 있는 상태에서 부모 엔티티에서 자식 엔티티의 참조를 제거해보자.

    @Autowired
    private EntityManager entityManager;
    @Test
    @Transactional
    void orphanRemoval() {
    	//given
        Option findOption = optionRepository.findById(1L).get();
        //when
        findOption.getOptionDetails().remove(0);
        entityManager.flush();
    }

그냥 조회 쿼리만 수행되는 것을 확인할 수 있다.


이제 orphanRemoval = true 옵션을 주고 다시 테스트를 수행하자.

    @Builder.Default
    @OneToMany(mappedBy = "option" ,fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OptionDetail> optionDetails = new ArrayList<>();

option_detail 테이블에서 삭제 쿼리가 실행되는 것을 확인할 수 있다.


물론 cascade와 orphanRemoval 옵션을 아무렇게나 사용하는 것은 권장하지 않는다.

 

1 : N : 1 관계나 @ManyToMany 관계의 경우에는, 엔티티의 경우 한쪽의 부모 엔티티의 영속화로 인해,

다른 부모 엔티티에서 자식 엔티티가 사라지는 케이스가 발생할 수 있기 때문이다.

 

엔티티간의 관계는 늘 복잡하고, 여러번 들어도 이렇게 정리해두지 않으면 오래 기억되지 않는 것 같다.


참고1 : https://hongchangsub.com/jpa-cascade-2/

참고 2 : https://willseungh0.tistory.com/67

참고 3 : https://resilient-923.tistory.com/417

 

[Spring/JPA] CascadeType.ALL 사용시 주의 해야 할 점

최근에 JPA를 본격적(?)으로 사용하면서 무릎을 탁! 쳤던 경험을 했습니다. 바로 연관관계 매핑, 영속성 전이, 연관관계 편의 메서드, 고아 객체를 이해하고 사용했던 경험인데요. 이번 시간에는 C

resilient-923.tistory.com

 

[JPA] 영속성 전이 Cascade란?, 고아 객체 제거 OrphanRemoval란?

JPA에 대해서 헷갈렸던 개념들을 위주로 정리하는 글입니다. 자바 ORM 표준 JPA 프로그래밍 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 JPA에서는 CASCADE

willseungh0.tistory.com

 

<JPA> 알고 쓰는 Cascade(영속성 전이)

이번 포스팅에서는 쓰면서도 헷갈리던 JPA의 Cascade 옵션에 대해서 정리하겠습니다.일단 기본적으로 Cascade라는 옵션이 등장하게 된 배경부터 알아봅시다. 아래의 코드를 보시죠 @Setter @Getter @Entity

hongchangsub.com

 

'개발 > Jpa' 카테고리의 다른 글

@Embedded, @Embeddable  (0) 2023.05.01
[JPA] JPA의 어노테이션(1)  (0) 2022.05.08
[Jpa]처음 접하는 Jpa  (0) 2022.02.27