JPA : CASCADE를 통한 영속성 전이, 고아 객체

    영속성 전이, CASCADE 


    • 영속성 전이는 연관관계를 맵핑하는 것과 전혀 관련 없음.
    • 엔티티를 영속화 할 때, 관련된 엔티티도 함께 영속화되는 편리함만 제공함.
    • CASCADE 옵션 붙은 쪽이 연관된 다른 엔티티를 관리한다. 

    CASCADE는 영속성이 전이된다는 말이다. 무슨 말이냐면, 특정 엔티티가 다른 엔티티와 관련이 있을 때, 특정 엔티티를 영속화하면 관련된 다른 엔티티도 영속화되는 것을 '영속성 전이'라고 한다. 이것은 연관관계와 즉시 로딩, 지연 로딩과 전혀 관계가 없다. 

    부모와 자식 클래스가 있다고 가정해보자. 예를 들어 부모 엔티티를 영속화하면, 자식 엔티티도 바로 영속화하고 싶을 때 쓸 수 있다. 또, 자식 엔티티를 영속화할 때 부모 엔티티도 바로 영속화 할 때 사용할 수 있다. 

     

     

    영속성 전이의 사용방법


    @ManyToOne(cascade = CascadeType.ALL)
    @OneToMany(mappedBy = "team", cascade = CascadeType.ALL)
    • @OneToMany, @ManyToOne의 어노테이션의 Cascade 옵션을 설정하면 가능하다.
    • Cascade 옵션에 따라 다양한 형태로 동작한다. 
    • 영속성 전이는 연관관계의 주인과 관계없이 전이된다. 

     

     

    영속성 전이, CASCADE : 다대일 연관관계에서 쓸 때


    Member member = new Member();
    member.setUsername("memberA");
    
    Team team = new Team();
    team.setName("teamA");
    member.setTeam(team);
    
    em.persist(member);

    위 코드로 영속성 전이에 대해서 알아보고자 한다. 가장 먼저 했던 것은 영속성 전이 기능을 On 하지 않았을 때, 위 코드가 실행되는지를 확인해보는 것이었다.

    실행결과 다음과 같은 에러메세지가 발생했다. 이유는 Member를 영속화하려고 했는데, Member에 속한 Team이 영속화되어있지 않아서다. 그래서 먼저 영속화를 하라는 메세지가 나온다. 그렇다면 Cascade를 On하면 어떻게 될까? 

    em.persist()는 단 한번 작성했으나, 영속성 전이로 인해 insert 쿼리가 2회 나간 것을 알 수 있다. 그리고 이 때, Member와 Team의 연관관계가 고려되어서, Member에는 정상적으로 TEAM_ID가 저장된 것을 볼 수 있다. 

     

    영속성 전이, CASCADE : 일대다 연관관계에서 쓸 때. 


    Member member1 = new Member();
    member1.setUsername("member1");
    
    Member member2 = new Member();
    member2.setUsername("member2");
    
    
    Team team = new Team();
    team.setName("teamA");
    team.addMember(member1); // 연관관계 편의 메서드. 둘다 추가해줌
    team.addMember(member2); // 연관관계 편의 메서드. 둘다 추가해줌
    em.persist(team);

    먼저 @OneToMany에서 Cascade 기능을 끄고 위의 코드가 실행되는지 확인했다. 

    코드는 정상적으로 실행되는 것을 확인했다. 연관관계의 주인은 Member다. 따라서 Team이 Members라는 가짜 맵핑을 가지고 있고, 이 맵핑에 Member가 추가되어도 저장에는 어떠한 영향도 미치지 못한다. 따라서 정상적으로 em.persist()는 1회만 나가서 DB에 정상적으로 저장되는 것을 확인했다. 

    또한 연관관계 편의 메서드인 add는 member.setTeam(team)이 되어있어서 각 Member 엔티티에 적절한 Team이 설정되어야한다. 그렇지만 보이지 않았다. 왜냐하면 Member는 영속화되지 않았기 때문이다. 

    이후에는 @OneToMany에 cascade 속성을 켜고 동일한 코드를 실행했다. 실행했을 때, insert 쿼리는 3번 나갔다. em.persist로 Team을 넣었을 때 영속성 전이가 되어 member1, member2도 함께 들어갔기 때문이다.

    이 때, 각 객체는 add라는 연관관계 편의 메서드에서 객체간의 참조가 저장이 완료되었기 때문에, 실제 DB에서는 정상적으로 맵핑이 된 값이 영속화되어 저장된 것을 볼 수 있다. 

     

    영속성 전이, CASCADE의 종류


    영속성 전이, CASCADE는 여러 사용 모드를 제공한다. 총 6개의 모드를 제공한다.

    • ALL : 모두에 적용
    • PERSIST : 영속화에만 적용
    • REMOVE : 삭제에만 적용
    • MERGE : 병합에만 적용
    • REFRESH : REFRESH에만 적용
    • DETACH : 준영속화 할 때만 적용

     

    영속성 전이, 언제 사용해야할까? 

    영속성 전이는 기본적으로 동일한 생명주기를 가질 때 사용을 고려해볼 수 있다. 대표적인 것은 엔티티의 소유자가 하나일 때만 쓰는 것이 권장된다. 예를 들어 MEMBER를 TEAM만 가진다고 가정하면, 영속성 전이를 사용을 고려해볼 수 있다. 그렇지만 MEMBER를 TEAM뿐만 아니라 LOCKER도 가진다면 사용을 하지 않는 것이 좋다.

    예를 들어 TEAM을 지울 때, MEMBER가 영속성 전이에 의하 같이 지워진다면 LOCKER가 나중에 MEMBER 엔티티를 쓰려고 할 때는 문제가 발생할 수 밖에 없다. 

     

    고아 객체란?


    고아 객체는 부모의 참조에서 삭제된 객체를 이야기한다. 예를 들어 부모 객체는 자식들이라는 필드 Collection을 가지고 있고, 여기에 자식1, 자식2가 있다고 가정하자. 여기서 자식1을 자식들 필드에서 삭제하면, 자식1은 부모 객체와 참조가 끊어진다. 즉, 이를 고아 객체라고 한다. 

     

    고아 객체의 자동 삭제?


    @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST,orphanRemoval = true)

    JPA에서는 한 객체가 고아 객체가 되었을 때, 자동으로 삭제가 될지 안 될지를 지원하는 모드가 있다. 이 모드를 켜게 되면 고아 객체가 되는 순간 Delete 쿼리가 나가며, DB에서 삭제가 되는 것을 확인할 수 있다. 고아 객체는 위에서 볼 수 있듯이 orphanRemoval에 true / false 값을 주어 선택할 수 있다.

     

    고아 객체 자동 삭제 관련 정리


    • 고아 객체의 자동 삭제는 orphanRemoval에서 설정할 수 있다.
    • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
    • 참조하는 곳이 하나일 때만 사용해야함. 
    • @OneToMany, @OneToOne에서만 사용 가능하다.

    참고할 사항은 부모를 제거하면 일반적으로 자식은 전부 고아가 된다는 것이다. 따라서 orphanRemoval = true로 설정하고 Parenet 객체를 삭제하면 그와 관련된 모든 Child 객체도 함께 제거된다. 

     

    고아 객체 관련 실습(cascade = Persist 설정), 하나만 삭제했을 때.


    Child child = new Child();
    child.setName("childA");
    Child child1 = new Child();
    child1.setName("childB");
    
    Parent parent = new Parent();
    parent.setName("parentA");
    parent.addChild(child1);
    parent.addChild(child);
    em.persist(parent);
    
    em.flush();
    em.clear();
    
    Parent parent1 = em.find(Parent.class, parent.getId());
    System.out.println("parent1 = " + parent1);
    parent1.getChildren().remove(0);

    두 명의 Child를 만들어 Parent에 저장한 후에, Parent의 Childern Collection에 저장된 첫번째 자식을 Children Collection에서 삭제했다. 기본적으로 연관관계의 주인은 Parent이기 때문에 위 코드는 연관관계를 고려하면, remove와 관련된 변화는 아무것도 없어야 한다. 

    그러나 실제 쿼리를 수행해보면 Child에 대한 delete 쿼리가 나가는 것을 볼 수 있고, DB에서도 한 Child가 삭제되었다. 앞서 이야기 했던 것처럼 이 Child 객체는 Parent 객체의 참조에서 제거되어, 다른 객체가 참조하지 않는 고아 객체로 판단되었다. 즉, 고아 객체로 판단되었기 때문에 orphanRemoval에 의해서 삭제된 것이다. 

    고아 객체 관련 실습(cascade = Persist 설정), 부모를 삭제했을 때


    Child child = new Child();
    child.setName("childA");
    Child child1 = new Child();
    child1.setName("childB");
    
    Parent parent = new Parent();
    parent.setName("parentA");
    parent.addChild(child1);
    parent.addChild(child);
    em.persist(parent);
    
    em.flush();
    em.clear();
    
    Parent parent1 = em.find(Parent.class, parent.getId());
    em.remove(parent1);

    위 코드는 부모 객체를 삭제해보는 코드다. 부모 객체를 삭제하면 기본적으로 Children에 있던 모든 child들은 고아 객체가 된다. 따라서 모두 삭제가 되어야 한다.

    실제로 Parent Delete 쿼리가 나갈 때, Childe Delete 쿼리도 함께 나가서 고아 객체가 모두 제거되는 것을 볼 수 있었다. 쿼리 결과물로 DB를 살펴보면, 이 테이블에 아무도 없는 것을 볼 수 있다. 

     

    영속성 전이 + 고아객체 동시 활성화


    • 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용히 사용한다.
    • 부모 엔티티로 자식 엔티티의 생명주기를 관리할 수 있음.
    • Cascade를 하면 부모 엔티티가 어떤 영속화를 할 때, 자식도 그 영속화가 같이 전이된다.
    • orphanRemoval = True를 하면 고아 객체가 된 것을 자동으로 지워준다

    Cascade를 ALL로 설정한다고 해도, 부모 객체에서 자식 객체를 지우는 것은 영속성 전이가 되지 않는다. 부모 객체가 지워지는 것이 아니라, 부모 객체에서 자식 객체의 참조가 제거되는 것이기 때문이다. 따라서 애초에 이 부분은 영속과는 무관하다. Cascade를 ALL로 설정하면 부모의 영속 상태가 바뀔 때, 자식의 영속 상태도 함께 바뀐다. 따라서, 부모가 삭제되면 자식도 같이 삭제되고 부모가 저장되면 자식도 같이 저장된다.

    그렇다면 부모 객체에서 자식 객체를 지울 때, 실질적으로 DELETE 쿼리가 나가는 것은 어떻게 해야할까? 여기서는 고아객체 삭제를 자동 모드로 설정해주어야 한다. 그렇게 할 경우, 부모의 참조에서 삭제된 자식은 고아객체로 판단되어 delete 쿼리가 나가서 자동으로 삭제된다. 

     

    영속성 전이, 고아객체 관련 정리


    • 영속성 전이, 고아객체 모두 연관관계와는 관련이 없다.
    • 영속성 전이는 부모 객체에 어떤 영속성 변경이 일어나면, 자식 객체에도 동일한 영속성 변경이 일어난다.
    • 고아객체는 부모 객체에서 참조가 제거 되었을 때, 어디서도 사용하지 않으면 고아객체라고 본다.
    • 고아 객체는 영속 상태와는 관련이 없다.
    • 영속성 전이 + 고아 객체를 함께 사용하면 부모 객체가 자식 객체를 관리하는 모드가 된다. 

     

    영속성 전이, 고아객체 연관관계와 관련없다는게 무슨 말?


    영속성 전이, 고아객체가 연관관계와 왜 관련이 없다고 말을 하는 것일까? 이들은 연관관계를 따라 움직이지 않기 때문이다. 예를 들어 영속성 전이로 특정 DB가 저장되는 단계는 이렇게 나눌 수 있다. 

    먼저 Parent / Child의 객체 상태에서의 연관관계 설정은 기존에 Mapping된 값들에 의해서 정해진다. 그리고 실제 메서드를 실행하는 시점에서 정해진다. 그렇다면 CasCade나 이런 것은 언제 실행이 된다는 것인가?

     

     

    댓글

    Designed by JB FACTORY