Per ardua ad astra !
I'm On My Way
Per ardua ad astra !
전체 방문자
오늘
어제
  • 분류 전체보기 (126)
    • Algorithm (50)
      • 백준 (30)
      • SWEA (3)
      • JUNGOL (3)
      • Programmers (5)
      • LeetCode (2)
    • 안드로이드 개발 (6)
      • Java로 개발 (0)
      • Kotlin으로 개발 (3)
    • Spring (41)
      • Spring기본 (17)
      • JPA기본 (15)
      • JPA활용 SpringBoot 기본 (9)
      • API 개발 기본 (0)
    • 네트워크 (3)
    • 운영체제 (0)
    • Life (3)
      • 책 (0)
      • 자기계발 (1)
      • 일상 (2)
    • 노마드코더 (3)
      • python으로 웹 스크래퍼 만들기 (3)
    • 프로그래밍 언어 (17)
      • Java 기본 (2)
      • 코틀린 기본 (15)

블로그 메뉴

  • 홈
  • 방명록

인기 글

hELLO · Designed By 정상우.
Per ardua ad astra !

I'm On My Way

JPA 종속 객체 관리 및 영속성 전이(CASCADE), 고아 객체
Spring/JPA기본

JPA 종속 객체 관리 및 영속성 전이(CASCADE), 고아 객체

2021. 7. 15. 15:22

영속성 전이: CASCADE


=> 특정 엔티티와 또 다른 엔티티가 종속관계이고 영속상태, 삭제상태 등 라이프 사이클을 어느정도 일치 시키고 싶을 때 사용하면 좋은 방법이다. 

@OneToMany, @OneToOne, @ManyToOne 
어노테이션 등에 속성으로 cascade를 두고 CascadeType을 설정해주면됨

 

 

CascadeType 종류


CascadeType.ALL: 모두 적용 (연관관계의 엔티티가 모든 라이프 사이클이 맞물린다면)
CascadeType.PERSIST: 영속할때 만 적용

=>특정 엔티티를 영속 상태(persist)로 만들 때 연관된 엔티티도 함께 자동으로 영속상태로 만들고 싶은경우 활용
CascadeType.REMOVE: 삭제할때만 적용

아래 세개는 잘 안씀
CascadeType.MERGE
CascadeType.REFRESH
CascadeType.DETACH


주의

- 영속성 전이는 연관관계 매핑과 아무런 관련이 없다.
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함만 제공한다.
- 만약 관계가 설정된 두 엔티티가 있고, 만약 이 엔티티중 하나가 다른 엔티티와 다시 관계가 설정되어 있는 경우. 쓰면안된다.
 주로 단일 엔티티에 종속적인 경우 사용하고 그렇지 않은경우 쓰지 말아야한다. 

꼭 종속적이고(단일 소유자 일때), 라이프 사이클이 유사할때 사용하는 것을 추천한다.

ex) Parent 엔티티와 child 엔티티가 있는데 (종속 관계 1:N) child 엔티티가 다른 엔티티와도 관계가 설정이 되어 있는경우

 

 

코드 예제

 

Parent

package jpabook.jpashop.domain;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class Parent {

    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<Child> childList = new ArrayList<>();

    public void addChild(Child child){
        getChildList().add(child);
        child.setParent(this);
    }

    public List<Child> getChildList() {
        return childList;
    }

    public void setChildList(List<Child> childList) {
        this.childList = childList;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

@OneToMany 어노테이션에 cascade = CascadeType.PERSIST 로 설정되어 있는 것을 확인할 수 있다.

자식과 양방향 매핑을 위해 mappedBy 속성까지 설정한 모습이다. 

 

Child

package jpabook.jpashop.domain;

import javax.persistence.*;

@Entity
public class Child {

    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne
    @JoinColumn(name = "PARENT_ID")
    private Parent parent;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Parent getParent() {
        return parent;
    }

    public void setParent(Parent parent) {
        this.parent = parent;
    }
}

 

 

JPAMain

package jpabook.jpashop;

import jpabook.jpashop.domain.*;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.locks.Lock;

public class JpaMain {

    public static void main(String[] args) {
        // start
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction(); // 트랙잭션
        tx.begin();

        // code
        try {
            Child child1 = new Child();
            Child child2 = new Child();
            
            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
            
            em.flush();
            em.clear();

            // child1 삭제 
            Child findChild = em.find(Child.class, child1.getId());
            em.remove(findChild);

	// 확인
            Parent findParent = em.find(Parent.class, parent.getId());
            List<Child> childList = findParent.getChildList();
            for (Child child : childList) {
                System.out.println("child1.id = " + child.getId());
            }

            tx.commit();

        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close(); // 사용 다하면 꼭 닫아줘야됨
        }
        // end
        emf.close();
    }
}

 

parent에서 cascade = CascadeType.PERSIST 를 설정하고 persist했기 때문에 child1, child2도 덩달아 persist가 완료되었을 것이다. 

 

그리고 나는 실험을 위해 영속성 컨텍스트를 다시 초기화 시킨 후 child1을 삭제해보았다. 그 결과 child1은 전혀 삭제가 되지 않았다.

 

(1) em.remove(child1) 했을 때 

 

child1(ID = 1L)이 삭제가 되지 않은 모습

이를 확인하고 CascadeType.PERSIST는 영속성 뿐만 아니라 '삭제와 관련된 생명주기까지도 따라가는구나, 정말 종속된 엔티티가 아니면 조심해야 겠다'고 생각했다.

 

(2)  List<Child> childList = findParent.getChildList()

      childList.remove(0); 

 

궁금증이 생겨서 부모가 설정한 양방햔 관계의 childList에서 자식 엔티티를 하나 제거하고 실행해 봤다

테이블 상에서는 제거가되지 않는다.

테이블 상에서는 제거가 안되지만, 리스트의 자식 객체 하나는 삭제되었다. 이는 데이터 무결성이 맞지 않아서 리스트에서 제거할때

-> child객체의 parent 속성을 null로 만들던가

-> 뒤에 배울 orphanRemoval 속성으로 자식 객체도 자동 삭제되도록 할 것인가

에 대한 설정을 하는 것이 좋다고 생각했다. 관계만 끊어지고 자식객체가 남아 있다면 null로 만드는 것이 나을 것이고, 완전 종속되어 부모객체의 리스트에서 삭제하면 자식객체의 데이터도 완전 삭제되도록 로직을 구현할 것인가는 더 신중히 고민해봐야 될 문제인 것 같다. 

 

(3) 부모객체와 매핑되지 않은 자식 객체의 삭제

 

만약 똑같은 상황에서 parent.addChild(child);를 실행하지 않은 자식 객체가 있다면 어떨까?

이 자식 객체의 경우 부모와 전혀 매핑이 안되어 있기때문에 em.remove(child) 코드도 실행이되어 삭제가 되고 부모 테이블로부터 독립된 상태로 자식 테이블에 존재한다.  

 

 


고아객체

 

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 고아 객체라고 한다. 위에 (2)에서 다루었듯이 부모의 리스트에서 자식 객체를 삭제했으면 자식 객체도 자동으로 삭제할 수 있는 방법이 존재한다.


사용방법

@OneToMany
@OneToOne
@ManyToOne 
어노테이션 등에 속성 orphanRemoval = true 으로 설정하면 된다.

 

Parent

@Entity
public class Parent {

    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<Child> childList = new ArrayList<>();

	// ... 생략
}

 

orphanRemoval은 false가 default값이고, true로 변경할 경우 부모 리스트에서 제거된 자식 객체의 엔티티 데이터는 자동 삭제된다. 

- 당연하게도 부모 객체가 제거되면 자식도 함께 제거된다. (CascadeType.REMOVE 처럼 동작한다)

- 꼭 참조하는 곳이 하나일 때 사용해야한다. (완전 종속)

- 꼭 특정 엔티티가 개인 소유할 때 사용해야한다. (완전 종속)

 

+ 더나아가 CascadeType.ALL + orphanRemoval = true 이면 부모 엔티티를 통해 자식 생명주기를 전적으로 관리할 수 있다.

 

이 게시물은 '자바 ORM 표준 JPA 프로그래밍' 강의를 수강하고 정리한 내용임을 밝힙니다. 
출처: https://www.inflearn.com/course/ORM-JPA-Basic#

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔

www.inflearn.com

 

 

 

    'Spring/JPA기본' 카테고리의 다른 글
    • 객체지향 쿼리 언어 소개(JPQL, QueryDSL, NativeQuery)
    • Embedded 타입 객체, JPA에서의 값 타입(참조기반) 수정시 주의사항, 값 타입 콜렉션
    • JPA 프록시 활용, 즉시로딩 vs 지연로딩(fetch = FetchType.LAZY)
    • 상속 관계 매핑 (Inheritance), 정보 상속(MappedSuperclass)
    Per ardua ad astra !
    Per ardua ad astra !
    개발자 지망생이며 열심히 공부하고 기억하기 위한 블로그입니다.

    티스토리툴바