4) 영속성 전이 :CASCADE (생각보다 많이 씀)
CASCADE 란? 즉시 로딩 레이지 로딩과 아무 관계도 아니다!
(헷갈릴 수는 있음)
특정 엔티티를 영속화 시킬 때 연관 엔티티도 함께 영속화 시키는 것을 말한다. 예시를 다음과 같이 보자.
@Entity
@Data
public class Parent {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
public void addChild(Child child) {
children.add(child);
child.setParent(this);
}
}
@Entity
@Data
public class Child {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
}
//
//
@Test
@DisplayName("CASCADE")
void test1() {
tx.begin();
try {
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
// Parent 중심으로 할 것이기 때문에, Child 는 Parent 영속화되면 좀 자동으로 되었으면 좋겠음
em.persist(parent);
em.persist(child1);
em.persist(child2);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
em.close();
}
emf.close();
}
persist 가 저렇게 점점 복잡해질 수도 있으니, 다음과 같이 해줄 수 있다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)\
private List<Child> children = new ArrayList<>();
로 바꿀 수 있다.
이제는 저 위에 child persist 들을 하지 않더라도, em.persist(parent) 만 하더라도 child 두개 모두 영속성 컨텍스트에 등록된다. Parent 안에 있는 Child(?) 또 상속관계는 아님. 그런 상황에서 한꺼번에 하는걸 cascade 라고 함.
>>> 영속성 전이는 매핑이랑 아무 상관 없다!
>>> ALL : PERSIST : REMOVE (ALL: 은 다 맞춰서 라이프 사이클 // Persist : 저장에 대해서만 맞춤, 그 이후로는 안맞춤)
그렇다고 1:N에는 다 걸면 되는거냐?? 그건 ㄴㄴㄴㄴ
만약에 N의 대상이 되는 애들이 그 1만이 관리한다. 그러면 CASCADE 쓰면 좋음. 하지만 그 N 대상을 관리하는 객체들이 여러개,
가령,
Teacher 라는 엔티티가 생기고, Teacher 가 Child 와 또다른 1:N 관계면 Cascade 안하는게 좋음.
왜냐면 그러면 알게 모르게 Query 들이 더 생길 수 있기 때문.
그냥 문제의 여지가 있는것은 안쓰는게 제~~일 깔끔함. 보통 이렇게 간단한 관계들이 아니라 진짜 개복잡함 엔티티들.
5) 고아 객체
> 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제.
다음과 같이 고아 객체 제거를 활성화 시킬 수 있다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> children = new ArrayList<>();
Parent 는 이 컬렉션을 관리하고 있는데, 이 컬렉션에서 빠지는 Child 객체들은 자동으로 삭제되어 DELETE 쿼리가 나가게 된다. 위에 예시에서 조금 더 연장을 해보자.
// Parent 중심으로 할 것이기 때문에, Child 는 Parent 영속화되면 좀 자동으로 되었으면 좋겠음
em.persist(parent);
// em.persist(child1);
// em.persist(child2);
// 그냥 제대로 쿼리 확인하기 위함
em.flush();
em.clear();
Parent parent1 = em.find(Parent.class, parent.getId());
parent.getChildren().remove(0);
tx.commit();
이와 같이 연장후 실행할 경우, child 중 하나가 db에서 삭제됨.
이거 조심해야하지만, 정말 쓰기도 함.
참조하는 곳이 하나일 때 사용하는 것, 특정 엔티티가 완전히 소유 대상일때만 사용해야 함. Cascade 와 비슷.
orphanRemoval 의 또하나의 특징은,
자식들을 모두 삭제함.
(약간 특정 객체들 삭제할 때 사용하기 좋을 듯??)
>> 가령, 위에서 parent1 을 삭제하면, parent1.getChildren 해서 조회가 가능한 모든 Child 객체들에 대한 DELETE 쿼리가 날라감. 위에서 em.remove(parent1) 을 하면 Parent 객체를 삭제하면서, 조회되는 Child 에 대한 DELETE 쿼리들도 다 날라간다.
6) 영속성 전이 + 고아 객체, 생명 주기
둘다 All, True 로 각각 켜주게 되면, 부모 엔티티를 통해서 자식의 생명 주기를 완벽히 통제할 수 있다. (다시 한번 말하지만 Parent 로 삼는 객체가 하나일 때만 써야함!! 안그러면 겁나 꼬임)
em.persist(parent);
em.remove(parent);
>> child 에 대해서는 하나도 제어하지 않았는데 DB에 저장되었다가, 삭제되었다가 함.
>> Child의 생명주기는 온전히 Parent 가 관리하게 되는 것임.
>> 이거가 나중에 도메인 주도 설계 (DDD) 의 Aggregate Root 개념 구현할 때 용이함. (아예 모르는 내용)
>> Repository의 제한적인 생성에 대한 내용임. Child 에 대한 Repo 를 하나도 생성하지 않고, Aggregate Root 가 Child를 관리하는 그런 거임. (즉, Parent 가 이 설계를 주도하는 것)
이상, 연관관계 및 프록시 끝~!~!
'Spring > JPA' 카테고리의 다른 글
[JPA] JPQL 쿼리 (0) | 2022.12.29 |
---|---|
[JPA] - 값 타입 (0) | 2022.12.29 |
[JPA] 프록시와 연관관계 정리 (0) | 2022.12.26 |
[JPA] 연관관계 매핑 -2 (0) | 2022.12.26 |
[JPA] - 엔티티 매핑 (1) | 2022.12.21 |