[JPA 기본] 8. 프록시와 연관관계 정리
프록시
- 프록시 등장 배경 : Member가 Team의 정보를 가지고 있다. 그런데 Member의 정보를 조회할 때 Team도 같이 조회되는 비효율 발생
- 해결법: Proxy, 지연 로딩
em.find vs em.getReference
em.find(): DB에서 실제 엔티티 조회
em.getReference: DB 조회를 미룬다. 대신 프록시 객체 조회
프록시 특징
- 실재 클래스 상속 받아 만듦.
- 프록시 객체는 실제 객체의 참조 보관
- 처음 사용시 한 번만 초기화
프록시 주의 사항
- 프록시 객체를 초기화할 경우, 실제 엔티티로 바뀌지 x.
- 타입 체크시 == 비교 대신 instance of 사용
- 영속성 컨텍스트에 내가 찾는 엔티티가 있다면, em.getReferene()도 실제 엔티티 반환
-> 이미 member 객체를 영속성 컨텍스트에 가져왔기 때문에 굳이 proxy로 대체할 필요가 없다.
-> JPA는 한 트랜젝션 안에서 두 객체가 같음 보장. 따라서 실제 엔티티 반환된다.
- 영속성 컨텍스트의 도움 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생. (실무에 자주 나온 문제) (프록시 35:23)
em.detach(refMember); // 영속성 컨텍스트에서 프록시 연결 해제
refMember.gewUerName; // could not initialize proxy 에러
지연로딩 vs 즉시 로딩
지연 로딩
- 엔티티 객체 호출시 내부 column에 매핑된 Entity에 대해, LAZY로 fetch 설정되어있으면, proxy 객체 사용
- 예) member 로딩 후 team1 지연 로딩 (=프록시 team1 엔티티). 실제 team1 사용하는 시점에 team1 엔티티 조회 쿼리 실행
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
즉시로딩
- MEMBER 조회시 TEAM 한번에 가져옴. ->Join 쿼리 사용. proxy 객체는 사용 x
- 애플리케이션 단에서 team과 member 조회가 같이 필요한 경우가 많을 경우 유리.
@ManyToOne(fetch = FetchType.EAGER)
private Team team;
프록시와 즉시로딩 주의점
- 지연 로딩만 사용하자. (특히 실무)
1) Member만 조회시 관련된 EAGER Entity 전부 긁어오므로 성능 저하
2) 즉시로딩 적용시 JPQL에서 N+1 문제 일으킬 수 있다.
예. member 조회시, member 조회 쿼리 1회, team 조회 쿼리 1회 실행. (총 2번=N+1)
- @ManyToOne, @OneToOne은 default값이 즉시로딩이므로 LAZY로 설정
cf. @OneToMany, @ManyToMany는 default값이 지연 로딩.
N+1 해결책
default) 모두 지연 로딩으로 설정
방법1) fetch join -> 대부분 이걸로 해결
방법2) 엔티티 그래프
방법 3) 배치 사이즈 (1+1)
영속성 전이: CASCAD
언제 사용?
특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용.
예. Parentf를 persist할 때, childList의 child도 모두 persist 할 거야.
// Parent Entity 내 설정 정보
@OneToMany(mappedBy="parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
영속성 전이 사용 가능한 경우
- 특정 부모 Entity에서만 자식 Entity가 연관되는 경우. (= 단일 엔티티에 종속적일 때)
- Parent와 Child 의 라이프 사이클 동일 (생성, 삭제)
고아 객체
- 부모 Entity로부터 연관관계 끊어진 자식에 대해 delete 쿼리 날아감.
- 특정 Parent에서만 해당 자식 참조시 사용 가능
// Parent Entity 내 설정 정보
@OneToMany(mappedBy="parent", orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
"영속성 전이 + 고아객체"의 생명주기
- CascadeType.ALL + orphanRemoval = true
- em.persist()로 영속화. em.remove()로 제거
- 부모 엔티티를 통해 자식의 생명 주기 관리 가능
- DDD의 Aggregate Root 개념 구현시 유용.