개발/JPA

[JPA 기본] 8. 프록시와 연관관계 정리

Dahee Joy Cha 2022. 1. 22. 11:08

프록시 

  • 프록시 등장 배경 : 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 개념 구현시 유용.