[자바 ORM 표준 JPA] JPA 영속성 전이(CASCADE)와 고아 객체

[자바 ORM 표준 JPA] JPA 영속성 전이(CASCADE)와 고아 객체

영속성 전이(CASCADE)와 고아 객체


목록#


  • 영속성 전이
  • 고아 객체

영속성 전이#

앞에서 나온 즉시 로딩, 지연 로딩, 연관관계 세팅 이 세가지와 완전 별개의 개념

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을때
  • 예 : 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장.

contact

영속성 전이: 저장#


@OneToMany(mappedBy="parent", cascade=CasecadeType.PERSIST)

contact

Parent.java

package relativemapping;

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

@Entity
public class Parent {

    public Parent() {
    }


    @Id
    @GeneratedValue
    @Column(name = "parent_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "parent")
    private List<Child> childList = new ArrayList<>();

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

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }
}

Child.java


package relativemapping;

import javax.persistence.*;

@Entity
public class Child {

    public 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.java - 자식 2개와 부모 객체 1개 생성

package relativemapping;

import org.hibernate.Hibernate;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.List;

public class JpaMain {
    //psvm 단축키로 생성 가능
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("relativemapping");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        tx.begin(); // [트랜잭션] 시작

        try{

            Child child1 = new Child();
            Child child2 = new Child();

            Parent parent = new Parent();

            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
            em.persist(child1);
            em.persist(child2);

            tx.commit();



        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();
    }


    private static void printMember(Member member){
        System.out.println("username = "+member.getUsername());
    }

    private static void printMemberAndTeam(Member member){
        String username = member.getUsername();
        System.out.println("username = "+username);

        Team team = member.getTeam();
        System.out.println("team = "+team.getName());
    }
}

console

Hibernate: 
    /* insert relativemapping.Parent
        */ insert 
        into
            Parent
            (name, parent_id) 
        values
            (?, ?)
Hibernate: 
    /* insert relativemapping.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)
Hibernate: 
    /* insert relativemapping.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)


간략하게 부모 엔티티 1개와 자식 엔티티 2개를 한번에 저장하기 위한 예제를 작성했고 저장을 위해서 em.persist()가 각각 3번 사용된 것을 확인할 수 있다.
현재 개발은 Parent 중심으로 개발하고 싶은데, 부모를 저장할때 자식까지 같이 관리해 주었으면 좋겠다. 라고 할때 만약 부모 이외에 em.persist() 를 지우면

  • JpaMain.java - 자식 2개와 부모 객체 1개 생성

package relativemapping;

import org.hibernate.Hibernate;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.List;

public class JpaMain {
    //psvm 단축키로 생성 가능
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("relativemapping");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        tx.begin(); // [트랜잭션] 시작

        try{

            Child child1 = new Child();
            Child child2 = new Child();

            Parent parent = new Parent();

            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
            //em.persist(child1); // ** 제거
            //em.persist(child2); // ** 제거

            tx.commit();



        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();
    }


    private static void printMember(Member member){
        System.out.println("username = "+member.getUsername());
    }

    private static void printMemberAndTeam(Member member){
        String username = member.getUsername();
        System.out.println("username = "+username);

        Team team = member.getTeam();
        System.out.println("team = "+team.getName());
    }
}

console

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert relativemapping.Parent
        */ insert 
        into
            Parent
            (name, parent_id) 
        values
            (?, ?)

부모의 엔티티만 저장한 것을 확인할 수 있습니다.

Parent.java - @OneToMany(mappedBy = “parent”, cascade = CascadeType.ALL)

package relativemapping;

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

@Entity
public class Parent {

    public Parent() {
    }


    @Id
    @GeneratedValue
    @Column(name = "parent_id")
    private Long id;

    private String name;
	
    //@OneToMany(mappedBy = "parent")
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)  // **
    private List<Child> childList = new ArrayList<>();

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

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }
}

JpaMain.java - 애플리케이션 재시작

console

Hibernate: 
    /* insert relativemapping.Parent
        */ insert 
        into
            Parent
            (name, parent_id) 
        values
            (?, ?)
            
Hibernate: 
    /* insert relativemapping.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)
            
Hibernate: 
    /* insert relativemapping.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)

contact

Parent를 포함한 Child 객체 2개 모두 저장된것을 확인할 수 있습니다.

영속성 전이: CASCADE - 주의!#


  • 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐
  • 참조하는 곳이 하나일 때 사용해야함!
  • 특정 엔티티가 독점 소유할 때 사용

영속성 전이: CascadeType#


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

CascadeType 종류#

CascadeType 설명
CascadeType.ALL 부모 엔터티에서 자식 엔터티로 모든 작업을 전파
CascadeType.PERSIST 자식 엔티티까지 영속성 전달, 부모 엔티티를 저장하면 자식 엔티티도 저장
CascadeType.MERGE 자식 엔티티까지 병합 작업을 지속, 부모 엔티티와 자식 엔티티를 조회 후 업데으트
CascadeType.REMOVE 자식 엔티티까지 제거 작업을 지속, 연결된 자식 엔티티까지 엔티티 제거
CascadeType.REFRESH 데이터베이스로부터 인스턴스 값을 다시 읽어 오기(새로고침), 연결된 하위 엔티티까지 인스턴스 값 다시 읽어옴
CascadeType.DETACH 영속성 컨텍스트에서 엔티티 제거, 연결된 하위 엔티티까지 영속성 제거

고아 객체#


고아 객체 제거#


부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제

  • orphanRemoval = true
  • Parent parent1 = em.find(Parent.class, id);
    parent1.getChildList().remove(0); // 자식 엔티티를 컬렉션에서 제거
  • DELETE FROM CHILD WHERE ID = ?

Parent.java - @OneToMany(mappedBy = “parent”, cascade = CascadeType.ALL, orphanRemoval = true)

package relativemapping;

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

@Entity
public class Parent {

    public Parent() {
    }


    @Id
    @GeneratedValue
    @Column(name = "parent_id")
    private Long id;

    private String name;

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

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

    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 List<Child> getChildList() {
        return childList;
    }

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

JpaMain.java

            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();

            Parent findParent = em.find(Parent.class, parent.getId());
            findParent.getChildList().remove(0);

            tx.commit();

console

Hibernate: 
    /* insert relativemapping.Parent
        */ insert 
        into
            Parent
            (name, parent_id) 
        values
            (?, ?)
            
Hibernate: 
    /* insert relativemapping.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)
            
Hibernate: 
    /* insert relativemapping.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)
            
Hibernate: 
    select
        parent0_.parent_id as parent_i1_7_0_,
        parent0_.name as name2_7_0_ 
    from
        Parent parent0_ 
    where
        parent0_.parent_id=?
        
Hibernate: 
    select
        childlist0_.parent_id as parent_i3_2_0_,
        childlist0_.id as id1_2_0_,
        childlist0_.id as id1_2_1_,
        childlist0_.name as name2_2_1_,
        childlist0_.parent_id as parent_i3_2_1_ 
    from
        Child childlist0_ 
    where
        childlist0_.parent_id=?
        
Hibernate: 
    /* delete relativemapping.Child */ delete 
        from
            Child 
        where
            id=?

orphanRemoval = true를 해두면, 영속성 컨텍스트의 객체 컬렉션에서 제거된 객체는 자동적으로 Delete 문으로 제거되는 것을 볼 수 있습니다.

고아 객체 - 주의#


  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일 때 사용해야함!
  • 특정 엔티티가 독점 소유할 때 사용
  • @OneToOne, @OneToMany만 사용가능
  • 참고 : 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제가할 때 자식도 함께 제거된다. 이것은 CasecadeType.REMOVE처럼 동작한다.

Parent.java - cascade = CascadeType.ALL 제거
@OneToMany(mappedBy = “parent”, orphanRemoval = true)

package relativemapping;

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

@Entity
public class Parent {

    public Parent() {
    }


    @Id
    @GeneratedValue
    @Column(name = "parent_id")
    private Long id;

    private String name;

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

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

    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 List<Child> getChildList() {
        return childList;
    }

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

JpaMain.java
casecadeType가 지워졌기 때문에 child1,2 em.persist()로 저장, em.remove(findParent); 를 통하여 부모 삭제


            Child child1 = new Child();
            Child child2 = new Child();

            Parent parent = new Parent();

            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
            em.persist(child1); // **
            em.persist(child2); // **

            em.flush();
            em.clear();

            Parent findParent = em.find(Parent.class, parent.getId());

            em.remove(findParent); // ** 
            
            tx.commit();

console

Hibernate: 
    /* insert relativemapping.Parent
        */ insert 
        into
            Parent
            (name, parent_id) 
        values
            (?, ?)
Hibernate: 
    /* insert relativemapping.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)
Hibernate: 
    /* insert relativemapping.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)
Hibernate: 
    select
        parent0_.parent_id as parent_i1_7_0_,
        parent0_.name as name2_7_0_ 
    from
        Parent parent0_ 
    where
        parent0_.parent_id=?
Hibernate: 
    select
        childlist0_.parent_id as parent_i3_2_0_,
        childlist0_.id as id1_2_0_,
        childlist0_.id as id1_2_1_,
        childlist0_.name as name2_2_1_,
        childlist0_.parent_id as parent_i3_2_1_ 
    from
        Child childlist0_ 
    where
        childlist0_.parent_id=?
Hibernate: 
    /* delete relativemapping.Child */ delete 
        from
            Child 
        where
            id=?
Hibernate: 
    /* delete relativemapping.Child */ delete 
        from
            Child 
        where
            id=?
Hibernate: 
    /* delete relativemapping.Parent */ delete 
        from
            Parent 
        where
            parent_id=?

부모를 지웠기 때문에 부모를 잃은 고아 객체들도 자동으로 삭제가 된 것을 확인할 수 있습니다.

영속성 전이 + 고아 객체, 생명주기#


  • CascadeType.ALL + orphanRemoval=true
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
  • 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있음
    • 부모가 생성주기를 관리하기 때문에 DAO나 Repository를 생성안해도됨
  • 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용

참고#