728x90
반응형
CASECADE
- ALL : 전체 적용
- PERSIST : 영속
- REMOVE : 삭제
- MERGE : 병합
- REFRESH : REFRESH
- DETACH : DETACH
@Entity
@Getter @Setter
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> childList = new ArrayList<>();
/* 연관관계 편의 메소드 - childList에 값 추가, parent값 초기화 */
public void addChild(Child child) {
childList.add(child);
child.setParent(this);
}
}
@Entity
@Getter @Setter
public class Child {
@Id
@GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
}
@SpringBootTest
@Transactional
@Commit
class DemoApplicationTests {
@PersistenceContext
EntityManager em;
@Test
void cascadeTest() {
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent); //Parent Persist
em.persist(child1); //Child1 Persist
em.persist(child2); //Child2 Persist
}
}
만약 Child에 대한 persist를 진행하지 않고 Parent에 대한 persist한번에 Child까지 삽입하고 싶다는 생각이 들때,
그때 필요한 것이 CASCADE옵션이다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
@Entity
@Getter @Setter
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) //cascade ALL옵션 추가
private List<Child> childList = new ArrayList<>();
public void addChild(Child child) {
childList.add(child);
child.setParent(this);
}
}
@SpringBootTest
@Transactional
@Commit
class DemoApplicationTests {
@PersistenceContext
EntityManager em;
@Test
void cascadeTest() {
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent); // Parent만 Persist
}
}
[결과] Parent만 Persist했으나 Child도 함께 Persist되었다.
insert
into
parent
(name, parent_id)
values
(NULL, 1)
insert
into
child
(name, parent_id, child_id)
values
(NULL, 1, 2)
insert
into
child
(name, parent_id, child_id)
values
(NULL, 1, 3)
CASCADE 영속성 정의는 엔티티를 영속화 할 때 연관된 엔터티도 함께 영속화 하는 편리함을 제공하는것일 뿐 연관관계를 매핑하는것과는 아무 관련이 없다.
하나의 부모가 자식들을 관리할 때에는 의미가 있다.
쉽게 말해 게시판과 첨부파일 (경로 등의 데이터) 첨부파일의 경로는 한 게시물에서만 관리하기 때문이다.
예를들어 파일을 여러곳 (다른 엔터티) 에서 관리를 하게되면 사용하면 안된다.
다른 엔터티가 해당 엔터티와 연관관계가 있다면 사용하면 안된다.
(운영이 매우 어려워진다.)
단일 엔터티에 완전히 종속적일 때에는 사용해도 무방하다.
- 그렇지 않을 때에는 등록 삭제의 라이프 사이클이 같을때
- 단일소유자 - 종속되는 엔터티의 소유자가 하나일때
고아 객체
- 고아객체 제거 : 부모 엔터티와 연관관계가 끊어진 자식 엔터티를 자동으로 삭제한다.
- @OneToMany 어노테이션에 orphanRemoval = true 옵션 추가.
- orphanRemoval : 고아 제거 라는 뜻
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
- Parent 객체를 조회한 뒤 반환된 객체의 childlist 리스트객체에서 특정 child객체를 제거한다.
Parent parent = em.find(Parent.class, id);
parent.getChildList().remove(0);
결과적으로 Child 엔터티에서 제거하려는 child를 제거하게된다.(Delete쿼리 발생)
Parent parent1 = em.find(Parent.class, parent.getId());
parent1.getChildList().remove(0);
- ManyToOne <=> OneToMany 양방향 관계에서 성립이 된다.
앞서 양방향 관계에서 배운 내용에 의하면 본질적으로 1에 해당하는 연관관계 엔터티는 M을 조회만 하지만,
cascadeType옵션을 주게되면 1쪽에서도 M에 해당하는 데이터를 함께 삽입할 수 있게 되며,
orphanRemoval옵션을 주게되면 1쪽에서도 M에 해당하는 엔터티를 삭제할 수 있게 된다.
단, 연관된 엔터티도 함께 삽입하거나, 함께 삭제를 함께하는것일 뿐이지 연관관계 매핑과는 관련이 없다. - 특정 엔티티가 개인 소유할때 그리고 참조하는 곳이 하나일때 사용해야 한다.
- 부모를 제거하면 자식은 고아가 된다.
따라서 고아 객체 제거 기능을 활성화 하면 부모를 제거할 때 자식도 함께 제거가 된다.
(Cascade옵션과는 별개로 orphanRemoval만 true로 줘도 작동한다.)
@OneToMany(mappedBy = "parent", orphanRemoval = true)
- CascadeType.REMOVE와 동일하게 작동한다.
CascadeType.All 제거 , orphanRemoval = true 후 em.remove(parent);
Parent parent1 = em.find(Parent.class, parent.getId());
em.remove(parent1);
결과 : 모든 child가 제거됨.
영속성 전이 + 고아객체
- CascadeType.ALL + orphanRemoval = true
- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove로 제거할 수 가있다.
- 두 옵션을 모두 활성화 하면 부모 엔터티를 통해서 자식 생명주기를 관리할 수 있게 된다.
- CasecadeType.ALL의 경우 영속성 전이의 모든 옵션을 오픈하는것이다
- 부모 엔터티와 연관관계가 끊어진 자식을 지우기 위해서는 양방향 연관관계의 조건 그리고
CascadeType.PERSIST(ALL)옵션과 orphanRemoval = true 옵션이 필요하다.
연속성 전이가 되어있기 때문에 부모엔터티로 부터 영속성이 전이된다.
이때 고아객체 제거 옵션까지 켜져있으므로 부모 엔터티로부터 자식엔터티를 접근하여 삭제하는것은
연관된 자식엔터티를 삭제하는 것이므로 둘의 연관관계가 끊어지게 되고 자식 엔터티에서 데이터가 삭제가 된다. - 부모 엔터티가 삭제될때 연관된 자식 엔터티를 연쇄적으로 지우기 위해서는
양방향 연관관계의 조건 orphanRemoval = true옵션만 필요하다.
이것은 CascadeType.REMOVE 단일 옵션과 동일하다.
(CascadeType.ALL(PERSIST)과 연관없는 이유는 orphanRemoval은 고아객체를 제거하는 것이기 때문에 부모 엔터티가 삭제된다면 당연히 모든 자식들은 고아가 되기 때문이다.) - 따라서 두 옵션을 모두 활성화 하게 되면 부모 엔터티를 통해서 자식의 생명주기를 관리할 수 있게 되는것이다.
- CasecadeType.ALL의 경우 영속성 전이의 모든 옵션을 오픈하는것이다
- 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용하다.
- Aggregate Root
Repository는 Aggregate Root만 컨텍하고 나머지는 Repository를 만들지 않는다(?) 만들지 않는것이 더 낫다(?)
Aggregate Root 관련된 대상은 Repository를 만들지 않고 Aggregate Root를 통해서 생명주기를 관리한다.
- Aggregate Root
728x90
반응형
'Hibernate > JPA(EntityManager)' 카테고리의 다른 글
[JPA] EntityManager 기본 설정 및 개념 정의 (0) | 2023.07.14 |
---|---|
[JPA] UPDATE 변경감지와 병합 (DirtyChecking / Merge) (0) | 2023.07.14 |
[JPA] 즉시로딩/지연로딩(Eager/Lazy) 과 JPQL N+1 이슈 (0) | 2023.07.13 |
[JPA] JPA에서 Proxy 객체란? (0) | 2023.07.13 |
[JPA] 상속 관계 매핑 @Inheritance , @Discriminator____ (0) | 2023.06.30 |