김찬진의 개발 블로그
23/12/28 [즉시로딩, 지연로딩 VS 영속성 전이, 고아 객체] 본문
지연로딩
Member 클래스의 team 필드를 fetch = FetchType.LAZY 로 설정하면
엔티티 객체 member 조회할 때
(1차 캐시에 엔티티 객체가 없다면) team 프록시 객체 생성해 1차 캐시에 저장하여 반환한다.
실제 엔티티 객체가 아니라 프록시 객체를 사용하므로 team 에 대한 SELECT 쿼리를 보내지 않는다
이후 실제로 엔티티 객체를 사용해야 할 때, 영속성 컨텍스트에게 프록시 객체의 target 필드를 초기화하도록 요청하고 team 에 대한 SELECT 쿼리를 보낸다
동시에 실제 엔티티 객체를 생성하여 1차 캐시에 저장하고 반환한다.
그래서 실제 엔티티 객체를 사용할 수 있게 된다
즉시로딩
Member 클래스의 team 필드를 fetch = FetchType.EAGER 로 설정하면
엔티티 객체 member 조회할 때
(1차 캐시에 프록시 객체가 없다면) team 객체까지 SELECT JOIN 쿼리를 보내서 반환하고 1차 캐시에 저장하고 반환한다.
당연히 프록시가 아니라 실제 엔티티 객체를 반환한다
영속성 전이 (CASACADE)
지연로딩, 즉시로딩과 영속성 전이는 아예 관련없는 개념이다
영속성 전이는 부모 엔티티 객체를 영속화할 때 그에 딸린 자식 엔티티 객체도 자동으로 영속화시켜주는 편리 기능일 뿐이다!
예제 코드르 보자 (김영한 님의 JPA 1편 강의 예제 코드입니다)
다음과 같은 엔티티가 존재한다
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
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);
}
// getter, setter 생략
}
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
// getter, setter 생략
}
위 코드를 가지고 다음과 같이 예제 코드를 만들 수 있다
Child child1 = new Child(); // 자식 만들기
Child child2 = new Child();
Parent parent = new Parent(); // 부모 만들기
parent.addChild(child1); // 부모에 자식 set
parent.addChild(child2);
em.persist(child1); // 자식 저장
em.persist(child2);
em.persist(parent); // 부모 저장
만약 자식 만들고 부모 만들고 자식을 부모에 set 하고 em.persist() 하는 과정이 귀찮다면?!
자식 만들고 부모 만들고 parent 만 저장하면 child 는 알아서 저장하면 안될까?!
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) // 영속성 전이
private List<Child> childList = new ArrayList<>();
// 연관관계 편의 메서드
public void addChild(Child child) {
childList.add(child);
child.setParent(this);
}
// getter, setter 생략
}
그럼 다음과 같이 작성해도 child1 과 child2 가 알아서 영속화된다
Child child1 = new Child(); // 자식 만들기
Child child2 = new Child();
Parent parent = new Parent(); // 부모 만들기
parent.addChild(child1); // 부모에 자식 set
parent.addChild(child2);
em.persist(parent); // 부모 저장
CASCADE 종류
- ALL : 모든 라이프사이클 동기화
- PERSIST : 제일 많이 사용
- REMOVE : 위험
- MERGE
- REFRESH
- DETACH
CASCADE 유의할 점
- 고유한 parent 가 child 들을 관리할 때 CASCADE 를 사용해야 한다 (각기 다른 parent 가 child 들을 관리할 때 CASCADE 를 사용한하면 안된다)
- parent 와 child 의 라이프 사이클이 거의 같을 때 CASCADE 사용해야 한다
고아 객체
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하길 원한다면(DELETE 쿼리 날아가길 원한다면)?
orphanRemoval = true 속성을 부여하자!
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
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);
}
// getter, setter 생략
}
다음과 같이 연습할 수 있다
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.flush();
em.clear();
System.out.println("================ find ================");
Parent parentEntity = em.find(Parent.class, parent.getId());
System.out.println("================ remove ================");
parentEntity.getChildList().remove(0);
tx.commit();
다음과 같이 출력문을 확인할 수 있다
================ find ================
Hibernate:
select
parent0_.PARENT_ID as PARENT_I1_7_0_,
parent0_.name as name2_7_0_
from
Parent parent0_
where
parent0_.PARENT_ID=?
================ remove ================
Hibernate:
select
childlist0_.PARENT_ID as PARENT_I3_2_0_,
childlist0_.CHILD_ID as CHILD_ID1_2_0_,
childlist0_.CHILD_ID as CHILD_ID1_2_1_,
childlist0_.name as name2_2_1_,
childlist0_.PARENT_ID as PARENT_I3_2_1_
from
Child childlist0_
where
childlist0_.PARENT_ID=?
Hibernate:
/* delete hellojpa.casacade.Child */ delete
from
Child
where
CHILD_ID=?
실제 DB 모습이다 (parent 1번, child1 2번, child2 3번 -> child1 2번이 삭제된 모습이다)
고아객체 유의할 점
- 고유한 부모 엔티티가 자식 엔티티들을 관리할 때만 고아객체를 사용해야함
- 부모로부터 자식을 떼놓는 것뿐만 아니라, 부모 자체를 제거하면 고아객체가 발생한다. 이는 CascadeType.REMOVE 동작과 같다
CascadeType.ALL + orphanRemoval = true 한다면?
부모 엔티티를 통해 자식 엔티티 생명주기를 완벽히 관리할 수 있음
부모가 영속화되면 자식이 영속화되고
부모가 제거되면 자식이 제거됨
도메인 주도 설계의 Aggregate Root 개념을 구현할 때 유용
'1일1배움 > JPA (김영한 님)' 카테고리의 다른 글
23/12/28 [JPA 값 타입 : Collection type + 총정리] (0) | 2023.12.28 |
---|---|
23/12/28 [JPA 값 타입 : primitive type, Wrapper type, String type / Embedded type] (0) | 2023.12.28 |
23/12/27 [지연로딩과 즉시로딩] (1) | 2023.12.28 |
23/12/27 [em.find() 와 em.getReference() 의 차이] (1) | 2023.12.27 |
23/12/25 [다양한 연관관계 매핑 - FK 관리 주체, 양방향 연관관계 여부에 따라] (0) | 2023.12.25 |