본문 바로가기

Spring 정리/Spring JPA

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

반응형

* 개요

 

영속성과 고아객체는 DB에서 사용하는 CASCADE와 관련이 있습니다. DB에서 설정이 가능하지만, 이 관계는 또한 JPA를 사용해서 자바 코드단에서 설정할 수 있습니다. 이번 시간에 사용방법을 알아보겠습니다.

 

 

* 영속성 전이(CASCADE)를 사용하는 때는?

 

특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용합니다. 예를 들어, 부모 엔티티를 저장할 때, 자식 엔티티도 저장할 수 있습니다.

 

 

* DB SQL로 제약상태 추가하기

 

DB에서 테이블을 만들 때, 수정과 삭제를 전이시키기 위해서 CASCADE를 사용합니다. 조건은 부모 테이블의 PK칼럼을 참조해야하며, 부모의 PK 칼럼을 자손에서는 FK칼럼으로 등록해야 합니다. 예를들어, ON DELETE CASCADE 설정으로 부모 테이블의 행이 삭제되면, 해당 FK를 가진 자손테이블도 삭제가 됩니다.

 

-- ALTER로 CASCADE 추가하기
ALTER TABLE 테이블명
ADD CONSTRAINT 제약조건명
 FOREIGN KEY (자손테이블FK 칼럼)
 REFERENCES 부모테이블명(부모테이블PK 칼럼)
 ON DELETE CASCADE

 

 

*일반적인 엔티티 등록 예시

 

간단한 예시를 통해서 알아보기 위해 Parent, Child 클래스를 만들어보겠습니다.

@Entity
public class Parent {
  @Id @GeneratedValue
  private Long id;
  
  private String name;
  
  @OneToMany(mappedBy = "parent")
  List<Child> childList = new ArrayList<>();
  
  public void addChild(Child child) {
  	childList.add(child);
    child.setParent(this);
  }
}

@Entity
public class Child {
  @Id @GneratedValue
  private Long id;
  
  private String name;
  
  @ManyToOne
  @JoinColumn(name = "parent_id")
  private Parent parent;
}

 

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

 

 

Parent와 Child 각자 객체를 만들고 em.persist()를 합니다.

 

 

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

 

그 결과, 3개의 insert문이 실행되었습니다. 1개는 Parent이고 나머지 2개는 Child입니다. 다시한번 짚고 넘어가면 em.persist()는 총 3회입니다.

 

 

addChild()로 부모설정을 했기 때문에 CHILD 테이블의 2개 데이터는 PARENT_ID가 모두 65입니다.

 

 

*CASCADE를 통해서 부모 중심으로 엔티티 다루기

 

cascade를 사용하면 자식 엔티티 생성 생명주기를 부모가 관리할 수 있습니다. 만약에 내가 Parent 중심으로만 개발하고 싶다면, cascade를 설정해서, Parent만 em.persist()를 하면 됩니다.

 

 

따라서, Parent 클래스에서 아래와 같이 cascade를 추가합니다.

 

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

 

 

Parent parent = new Parent();
parent.setName("parent");

Child child1 = new Child();
child1.setName("child1");
child1.setParent(parent);

Child child2 = new Child();
child2.setName("child2");
child1.setParent(parent);

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

tx.commit();

 

 부모중심으로 엔티티를 다루기 때문에 child1, child2에는 em.persist()하지 않아도 됩니다. 단지, 부모만 em.persist()로 등록합니다. cascade = CascadeType.ALL 이므로 Child의 생명주기는 Parent에 의존하기 때문입니다.

 

 

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

 

정상적으로 3번의 insert가 수행됩니다.

 

 

어느 경우에 사용할까?

-> Child 테이블이 완전히 Parent에만 종속적일 때 사용합니다.

 

 예를 들어, 첨부파일을 보내고, 첨부파일 경로를 저장하는 테이블이 있다면, 게시글이 Parent, 그에 따른 첨부파일 경로 테이블이 Child로 들어갑니다. 만약에, Child가 Parent 뿐 아니라 다른 테이블에도 종속적이라면, 데이터가 꼬일 위험이 있습니다. 따라서, 완전종속의 경우 CASCADE를 쓰면 효과적입니다.

 

 영속성 전이는 연관관계를 매핑하는 것과 아무런 관련이 없습니다. 엔티티를 영속화 할 때, 연관된 엔티티를 같이 영속화 한다는 점에서 편리함을 제공하는 것입니다.

 

cascade의 종류로는 ALL(모두), REMOVE(삭제), PERESIST(영속) 등이 있습니다.

 

 

* 고아객체

 

고아객체란, cascade의 삭제와 관련이 있는데, cascade는 부모가 삭제되면 자식도 삭제되는 것과 달리, 고아객체는 부모와 연관관계가 "해제"되면 삭제됩니다. 즉, 부모의 삭제여부 관련 없이 고아가 되면 삭제됩니다.

 

Parent 클래스의 childList에 다음과 같이 고아객체를 설정해보겠습니다.

 

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

 

고아객체는 orphanRemoval = true 로 설정할 수 있습니다.

 

Parent parent = new Parent();
parent.setName("parent");

Child child1 = new Child();
child1.setName("child1");
child1.setParent(parent);

Child child2 = new Child();
child2.setName("child2");
child2.setParent(parent);

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

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

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

tx.commit();

 

 고아객체만의 특성을 확인하기 위해 savedParent.getChildList().remove(0); 로 부모가 가지고 있는 chlid 연관관계 리스트에서 첫번째 자식을 "분리"하였습니다.

 

 

em.flush()결과

 

cascade 예제처럼 parent와 child 2개가 동일하게 생성됩니다.

 

 

savedParent.getChildList().remove(0) 결과

 

부모가 삭제되지 않았지만, child 엔티티는 고아가 되었으므로 테이블에서 삭제됩니다.

 

 

 Parent에서 Child가 분리되었을 뿐인데, delete 쿼리가 나갑니다. orphanRemoval = true이기 때문입니다.

고아 객체 또한 특정 엔티티가 개인소유할 때만 사용합니다. 참조가 1개일때만 사용해야 합니다. @OneToOne, @OneToMany에서만 사용이 가능하다.

 

부모를 제거하면 자식은 고아가 되므로, 따라서 부모를 삭제하면 자식 또한 고아가 되어 삭제됩니다.

 

Cascade ALL + orphanRemoval = true 조합을 사용하면, 자식의 생명주기는 부모가 완전히 통제할 수 있습니다.

부모는 em.persist()em.remove()로 생명주기가 관리됩니다.

 

 

*정리

- cascade의 삭제는 부모가 삭제되어야 자식도 삭제된다.

- orphanRemoval은 부모 삭제 없이 자식이 부모와 분리만 되어도 삭제된다.

 

* 참고

 

자바 ORM 표준 JPA 프로그래밍 - 기본편

반응형

'Spring 정리 > Spring JPA' 카테고리의 다른 글

페치 조인 ( fetch join ) -1  (0) 2021.10.05
N + 1 문제  (0) 2021.09.30
JPA 프록시(+즉시로딩, 지연로딩 비교)  (0) 2021.09.14
N:M 테이블 관계 설계하기  (0) 2021.09.13
연관관계 매핑  (0) 2021.09.10