영속성 전이(CASCADE)와 고아 객체

2022. 5. 25. 15:08Java/JPA

영속성 전이에 대해 많은 오해들이 있습니다. 연관관계 매핑이나 즉시, 지연 로딩과 어떠한 관계가 있을 것이라고 생각하는데요.

이번 시간에 영속성 전이에 대해 알아보면서 왜 쓰이고 언제 쓰이는지 그리고 주의점은 어떤 게 있는지 정리해보겠습니다. 

 

영속성 전이

 

언제 쓰이는가?

특정 Entity를 영속 상태로 만들 때, 연관된 Entity도 함께 영속 상태로 만들고 싶을 때 사용합니다.

    예) 부모 Entity 저장 시 연관된 자식 Entity도 함께 저장할 때 쓰임

 

코드로 이해해 보겠습니다.

@Entity
@Getter
@Setter
public class Parent {

	@Id
	@GeneratedValue
	private Long id;

	private String name;
	
	@OneToMany(mappedBy = "parent")
	private List<Child> childList = new ArrayList<>();

  	// 연관관계 편의 메서드 - 핵심적으로 컨트롤 하는 쪽에 위치
	public void addChild(Child child) {
		childList.add(child);
		child.setParent(this);
	}
}
@Entity
@Getter
@Setter
public class Child {

	@Id
	@GeneratedValue
	private Long id;

	private String name;

	@ManyToOne
	@JoinColumn(name = "parent_id") // 연관관계 주인
	private Parent parent;
}

 

위 경우 Child를 Parent에 호출할 때, EntityManager에서 persist를 각각에 대하여 호출해줘야 합니다.

Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent); 
em.persist(child1);
em.persist(child2);

이렇게 persist를 3번 하면 귀찮기도 하고

parent중심으로 코드를 짜고 parent가 persist 될 때 자식들도 persist가 동시에 되었으면 좋겠다!

 

 

이때 사용하는 게 Cascade입니다.

Parent Class에서 Cascade 설정 시, Child 모두 같이 저장이 가능합니다.

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



Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent); // 셋 다 persist됨

 

 

CASCADE 종류

ALL: 모두 적용
PERSIST: 영속
REMOVE: 삭제
MERGE: 병합
REFRESH: REFRESH
DETACH: DETACH

All, Persist를 주로 사용합니다.

  • All은 라이프사이클을 다 맞출 때
  • Persist는 저장할 때만 맞추고 나머지는 따로 쓸 때

 

영속성 전이 오해 

  1. 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
  2. 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐입니다.

 

그럼 영속성 전이는 언제 사용하는가?

하나의 부모가 자식들을 관리할 때 의미 있습니다. == 소유자 하나 일 때

만약, 다른 엔티티와 자식이 관계가 있을 땐 쓰면 안 되고 이땐 따로 관리하는 게 맞습니다.

조건 두 가지 모두 만족할 때 사용합니다.
1. 부모와 자식의 라이프 사이클 거의 유사할 때
2. 단일 소유자 일 때

 

 

 

고아 객체

부모 Entity와 연관관계가 끊어진 자식 Entity를 자동으로 삭제합니다.

orphanRemoval=true 시 해당 고아 객체를 제거

  • 연관 관계를 제거하거나, 부모 객체가 제거되는 경우 자식 객체가 제거됩니다.
@Entity
@Getter
@Setter
public class Parent {

	@Id
	@GeneratedValue
	private Long id;

	private String name;
	
	@OneToMany(mappedBy = "parent", cascade = CascadeType.All, orphanRemoval = true)
	private List<Child> childList = new ArrayList<>();

 
	public void addChild(Child child) {
		childList.add(child);
		child.setParent(this);
	}
}
Parent findParent = em.find(Parent.class, parent.getId());
findParent.getChildList().remove(0); // 0번 Child의 Parent 연관 관계 제거, 자식 엔티티를 컬렉션에서 제거

//이렇게 하면 delete 쿼리 나가서 지워짐

 

 

고아 객체 주의사항

참조하는 곳이 하나일 때 사용해야 하며, 특정 Entity가 자식 Entity를 개인 소유할 때만 사용

  • @OneToOne, @OneToMany만 가능
[참고]
고아 객체 제거 기능을 활성화하면, 부모를 제거할 때 자식도 함께 제거된다.
이것은 CascadeType.REMOVE처럼 동작합니다.
  • 개념적으로 부모를 제거하면 자식은 고아가 됩니다.

 

 

영속성 전이 + 고아 객체

CascadeType.ALL + orphanRemoval=true 의미
JPA를 통해 스스로 생명주기를 관리하는 Entity는 em.persist()로 영속화, em.remove()로 제거하는데요,
만약, 상기 두 옵션을 모두 활성화하면 부모 Entity를 통해 자식 Entity의 생명주기를 관리할 수 있습니다.
💡 언제 유용하게 사용될까?
도메인 주도 설계(DDD)의 Aggregate Root 개념 구현 시 유용합니다.

 

 

정리

  1. 글로벌 Fetch 전략 설정
    • 모든 연관관계를 지연 로딩으로 설정
    • @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 지연 로딩으로 변경
  2. 영속성 전이 설정
    • 단일 부모 Entity에 종속되는 자식 Entity는 Cascade Type을 All로 설정

 

 


 

 

JPA기본편 스터디 함께한 Jarry, Matt 항상 감사합니다.

 

 

참고 :

https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com