스프링-cascadeType.ALL 실험해보기

이진우·2023년 10월 1일
0

스프링 학습

목록 보기
17/41

들어가기 전에

JPA 강의를 보았다면
cascade = CascadeType.ALL을 쓴다면 특정 Entity(A)에 em.persist(Entity)를 하지 않았더라도 그와 연관된 엔티티(B)에서 세팅을 해주기만 하면 자동으로 A까지 테이블에 저장되고 들어갈 수 있었습니다.
근데 이를 바탕으로 궁금증이 생겨서 여러 가지 실험을 했습니다.

원래의 기본 코드입니다.

parent.java

@Entity
public class Parent {
    @Id @GeneratedValue
    private Long id;

    private String name;

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


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

    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;
    }
}

child.java

@Entity
public class Child {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Parent parent;

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

    public Parent getParent() {
        return 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;
    }
}

main

      Parent parent=new Parent();
            parent.setName("부모1");
            em.persist(parent);
          Child child=new Child();
          child.setName("아이1");
          Child child2=new Child();
          child2.setName("아이2");
          parent.addChild(child);
          parent.addChild(child2);

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

       /*   Parent findParent=em.find(Parent.class,parent.getId());
          em.remove(findParent);*/
            System.out.println("==============");





            tx.commit();

이것이 기본 코드입니다.

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

child 에 대한 em.persist를 안해주었었지만 parent의 addChild의 하나로 child가 insert 되는 것을 볼 수 있습니다.

실헙 1:연관관계 편의 메서드의 childList.add(child); 와 child.setParent(this); 중 뭐가 핵심적일까?

일단 연관관계 편의 메서드를 보면

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

이렇게 생겼습니다.
헷갈렸었던것이 JPA에서는 다대일에서는 항상 다 쪽이 연관관계의 주인이였습니다.

그래서 이때도

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

였음에도 불구하고 child.setParent(this)가 없으면 child에 대한 insert 쿼리가 안나갈 것이라고 처음에는 생각했습니다.
그런데

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

이렇게 코드를 수정했을때 여전히

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

이런 쿼리가 나갔고


'위와 같이 DB에는 Entity가 들어간다는 것을 확인할 수 있었습니다(parent_id가 null 인 이유는 다쪽에서 set 을 주석처리했기 떄문입니다)

그러면 이번에는 반대로

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

이렇게 ChildList.add(child)에 주석처리를 하면


/* insert jpa_basic_dionisos198.Parent
       */ insert 
       into
           Parent
           (name, id) 
       values
           (?, ?)
==============
10월 01, 2023 9:44:00 오

이렇게 parent 에 대한 insert 쿼리만 나가고 마무리가 됩니다.

실험 1 결론
@OneToMany(mappedBy = "parent",cascade = CascadeType.ALL)
    private List<Child> childList=new ArrayList<>();

cascade가 1쪽에 있을떄는

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

위 연관관계 편의 메서드의 childList.add(child) 가 자동으로 child 에 대한 insert 쿼리를 날려주는 핵심 코드입니다.

실험 2:ManyToOne에 cascade 써보자

지금까지 oneTomany 나 onetoOne에 cascadeType.all을 썼는데 이번에는 ManyToOne에 cascade 를 써보겠습니다.

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


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

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

위와 같이 parent 에서 주석처리를 해줌으로써 양방향 연관관계를 해제해줍니다.

대신 Child 에

@ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
    @JoinColumn(name = "parent_id")
    private Parent parent;

위처럼 CascadeType.ALL을 선택해 줍니다.

    Parent parent=new Parent();
            parent.setName("부모1");
           //em.persist(parent);
          Child child=new Child();
          child.setName("아이1");
          Child child2=new Child();
          child2.setName("아이2");
          child.setParent(parent);
          child2.setParent(parent);
          em.persist(child);
          em.persist(child2);

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

       /*   Parent findParent=em.find(Parent.class,parent.getId());
          em.remove(findParent);*/
            System.out.println("==============");





            tx.commit();

main 을 위 코드 처럼 바꾸면 em.persist(parent)가 없어도 parent 가 자동으로 들어갑니다.

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

당연히 DB에도 아래와 같이

잘 들어갑니다.

또한 삭제 역시

main 에

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

          Child findChild1=em.find(Child.class,child.getId());
          Child findChild2=em.find(Child.class,child2.getId());

          em.remove(findChild1);
          em.remove(findChild2);

이 코드를 추가할시

Hibernate: 
    /* delete jpa_basic_dionisos198.Child */ delete 
        from
            Child 
        where
            id=?
Hibernate: 
    /* delete jpa_basic_dionisos198.Parent */ delete 
        from
            Parent 
        where
            id=?
Hibernate: 
    /* delete jpa_basic_dionisos198.Child */ delete 
        from
            Child 
        where
            id=?

이렇게 Child 2개가 지워지면 parent 역시 지워진다는 것을 확인할 수 있습니다.

실험 2 결론

@ManyToOne에도 cascade 를 쓸수 있다.

다쪽 Entity 즉 @ManyToOne 에 cascadeType.all을 추가할 시에는 다 쪽 엔티티에서 setTeam(1쪽)으로 1을 설정하면 Team(1쪽) 에 대한 persist를 안해주어도 알아서 Team 테이블에 저장이 되고, 삭제할 때는 한 팀(1쪽) 에 대한 Member(다)들이 모두 사라지면 그 팀도 delete 됩니다.

반면 @OneToMany에 cascadeType.all 이 있다면 addlist를 통해서 자동으로 다쪽에 대한 insert 쿼리를 내고, 1쪽(team)을 없애면 그 team 을 가진 member(다)가 모두 사라집니다.

위 결과를 바탕으로 유의 할점

예를 들어 다대다를
1대 다 다대 1 로 쪼갰다고 하면

Member

   @OneToMany(mappedBy = "member",cascade = CascadeType.ALL,orphanRemoval = true)
    private List<MemberProduct> memberProducts=new ArrayList<>();

    public void addProducts(MemberProduct memberProduct){
        memberProducts.add(memberProduct);
        memberProduct.setMember(this);

    }

에 이런 코드가 존재하고
중간테이블로는
MemberProduct

@Entity
public class MemberProduct {
    @Id @GeneratedValue
    private Long id;

    public Long getId() {
        return id;
    }

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

    public Member getMember() {
        return member;
    }

    public void setMember(Member member) {
        this.member = member;
    }

    @ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
    @JoinColumn(name = "member_id")
    private Member member;


    @ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
    @JoinColumn(name = "product_id")
    private Product product;

    private int orderAmount;
    private Date orderDate;
    public MemberProduct(Product product){
         this.product=product;
    }
    public MemberProduct(){

    }
}

Product

@Entity
public class Product {
    @Id @GeneratedValue
    private Long id;

    private String name;

    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(mappedBy = "product")
    private List<MemberProduct> memberProducts=new ArrayList<>();

}

이렇게 있다 치고

 Product product=new Product();
            product.setName("아이템1");
            em.persist(product);

            Member member=new Member();
            member.setName("jinu");
            member.addProducts(new MemberProduct(product));
            em.persist(member);
            em.flush();
            em.clear();

            Member findMember=em.find(Member.class,member.getId());
            em.remove(findMember);





            tx.commit();

이렇게 코드를 짜면 망한다.
em.remove(findMember)를 통해서 중간테이블의 Member의 ID를 가진 것이 지워지는 것은 괜찮은데 그게 지워지면서 cascade가 걸려서 item까지 지워버리는 현상이 일어나기 때문에 유의 해야 한다.

profile
기록을 통해 실력을 쌓아가자

0개의 댓글