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 되는 것을 볼 수 있습니다.
일단 연관관계 편의 메서드를 보면
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 쿼리만 나가고 마무리가 됩니다.
@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 쿼리를 날려주는 핵심 코드입니다.
지금까지 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 역시 지워진다는 것을 확인할 수 있습니다.
@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까지 지워버리는 현상이 일어나기 때문에 유의 해야 한다.