참고
즉시 로딩과 지연 로딩
NOTE
회원 엔티티를 조회할 때, 연관된 팀 엔티티도 같이 조회되는 것이 좋을까? 아니면 회원 엔티티만 조회해두고 팀 엔티티는 실제 사용하는 시점에 조회하는 것이 좋을까?
•
이는 정확한 정답이 없고, 상황에 따라 달리질 수 있다.
•
이러한 방식을 설정하는 것이 즉시 로딩과 지연 로딩이다.
즉시 로딩
NOTE
즉시 로딩을 사용하면 Member를 조회하면 Team도 한번에 조인해서 둘다 조회한다.
@Entity
public class Member{
// ...
@ManyToOne(fetch = FetchType.EAGER) //즉시로딩 사용
@JoinColumn(name="TEAM_ID")
private Team team;
// ...
}
//...
Member m = em.find(Member.class, member1.getId()); //Member 객체 반환
System.out.println("m = "+ m.getTeam().getClass()); //Team 객체 반환
Java
복사
•
fetch = FetchType.EAGER
◦
즉시 로딩 EAGER를 사용해서 한번에 조회한다.
지연 로딩
NOTE
지연 로딩을 사용하면 Member를 조회하면 Team은 실제 사용시점에 조회한다.
@Entity
public class Member{
//...
@ManyToOne(fetch = FetchType.LAZY) //지연로딩 사용
@JoinColumn(name="TEAM_ID")
private Team team;
//...
}
//...
Member m = em.find(Member.class, member1.getId()); //Member 객체 반환
System.out.println("m = "+ m.getTeam().getClass()); // Team$HibernateProxy객체 반환
m.getTeam().getName() // team을 실제로 사용하는 시점에서 db조회 엔티티 반환
Java
복사
•
fetch = FetchType.LAZY
◦
지연 로딩 LAZY를 사용해서 프록시로 조회하고, 실제 사용시 실제 엔티티를 가져온다.
프록시와 즉시로딩 주의 (N+1 문제)
NOTE
N+1문제는 연관 관계가 설정된 엔티티를 조회할 경우, 연관된 엔티티 데이터 개수(n)만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어온다.
@Entity
public class Member{
// 즉시 로딩을 사용하는 경우
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name="TEAM_ID")
private Team team;
// ...
}
Java
복사
Member(1) - Team(N)의 관계라고 가정한다.
•
즉시 로딩을 실행하는 경우 Meber의 쿼리를 수행한 이후 Meber에 있는 Team의 데이터를 가져오기 위한 쿼리를 수행하게 된다.
•
이렇게 되는 경우 Meber가 속한 Team의 개수만큼 조회 쿼리가 추가적으로 발생하고 DB에 접근하는 횟수가 늘어나면서 성능저하의 문제가 발생한다.
JPA 기본 fetch 전략
•
@ManyToOne, @OneToOne은 기본이 즉시 로딩 → 전부 LAZY로 설정
•
@OneToMany, @ManyToMany는 기본이 지연 로딩
영속성 전이: CASCADE
NOTE
객체를 저장하거나 삭제할 때 연관된 객체도 함께 저장하거나 삭제할 수 있는데 이것을 영속성 전이라 한다.
•
JPA는 CASCADE 옵션으로 영속성 전이를 제공한다.
•
@OneToOne, @OneToMany에서만 사용가능
영속성 전이 - 저장
NOTE
객체 관계
영속성 전이 사용안함
// 부모 저장
Parent parent = new Parent();
em.persist(parent) ;
// 1번 자식 저장
Child child1 = new Child();
child1.setParent(parent); //자식 -> 부모 연관관계 설정
parent.getChildren().add(childl) ; //부모 -> 자식
em.persist(childl);
// 2번 자식 저장
Child child2 = new Child();
child2.setParent(parent); //자식 -> 부모 연관관계 설정
parent.getChildren().add(child2); //부모 -> 자식
em.persist(child2);
Java
복사
parent와 child 요소를 모두 각각 persit 해줘야함.
영속성 전이 사용
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<Child>();
Java
복사
cascade PERSIST로 설정
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
childl.setParent(parent) ; //연관관계 추가
child2.setParent(parent) ; //연관관계 추가
parent.getChildren().add(child1);
parent.getChildren().add(child2);
//부모저장, 연관된자식들저장
em.persist(parent);
Java
복사
연관객체의 persit 한번으로 모두 저장된다.
CASCADE 종류
NOTE
public enum CascadeType {
ALL, //모두 적용
PERSIST, //영속
MERGE, //병합
REMOVE, //삭제
REFRESH, //REFRESH
DETACH //DETACH
}
Java
복사
parent와 child 요소를 모두 각각 persit 해줘야함.
•
CascadeType.PERSIST, CascadeType,REMOVE는 em.persist(), em.remoce()를 실행할 때 바로 전이가 발생하지 않고, 플러시를 호출할 때 전이가 발생한다.
고아 객체
NOTE
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<Child>();
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0); //자식 엔티티를 컬렉션에서 제거
Java
복사
•
JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데, 이를 고아 객체 제거라고 한다.
•
부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거 → 자식 엔티티가 자동으로 삭제된다.
•
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제를 위한 기능이므로, @OneToOne, @OneToMany에서만 사용할 수 있다.
[ 참고]
•
개념적으로 부모를 제거하면 자식은 고아가된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. (CascadeType.REMOVE처럼 동작한다.)
•
Parent객체를 지우게 되면 Parent가 소유하고있는 ChildList에 속한 엔티티들이 전부 같이 삭제된다.
영속성 전이 + 고아 객체 → 생명주기
NOTE
CascadeType.ALL+ orphanRemoval = true ⇒ 부모 엔티티를 통해 생명주기를 관리한다.
•
일반적으로 엔티티는 EntityManager.persist()를 통해 영속화되고, EntityManager.remove()를 통해 제거된다.
•
이것은 엔티티 스스로 생명주기를 관리한다는 의미이다.
◦
그런데 두 옵션을 모두 활성화하면 부모 엔티티를 통해 자식 생명주기를 관리할 수 있다.
◦
이는 DDD의 Aggregate Root 개념을 구현할 때 편리하다.