본문 바로가기
Book

자바 ORM 표준 JPA 프로그래밍 - JPA 정리하기 (8-1) 프록시와 연관관계 정리

by devLog by Ronnie's 2022. 8. 27.

들어가며


8강은 1편과 2편으로 나눠 진행하였다. 

 

8.1 프록시

  • 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 지연 로딩이라 한다.
  • 그런데 지연 로딩 기능을 사용하려면 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체라 한다.
  • JPA 표준 명세는 지연 로딩의 구현 방법을 JPA 구현체에 위임 -> 하이버네이트
  • 하이버네이트는 지연 로딩을 지원하기 위해 프록시를 사용하는 방법과 바이트코드를 수정하는 두 가지 방법을 제공

8.1.1 프록시 기초

  • JPA 에서 식별자로 엔티티 하나로 조회할 때 find() 사용 -> 영속성 컨텍스트에 엔티티가 없으면 디비 조회 -> 이렇게 직접 조회하면 조회한 엔티티를 실제 사용하든 하지 않든 데이터베이스를 조회하게 된다. -> 엔티티를 실제 사용하는 시점까지 미룰때는 getReference()를 사용
  • getReference() 사용 시 디비를 조회하지 않고 실제 엔티티 객체도 생성하지 않는다 -> 대신 디비 접근을 위임한 프록시 객체를 반환
  • 프록시의 특징 1
    • 프록시 클래스는 실제 클래스를 상속 받아서 만들어지므로 실제 클래스와 겉 모양이 같음
    • 프록시 객체는 실제 객체에 대한 참조를 보관
    • 프록시 객체의 메서드를 호출하면 프록시 객체는 실제 객체의 메서드를 호출함
  • 프록시 객체의 초기화
    • 프록시 객체는 member.getName() 처럼 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성하는 것을 프록시 객체의 초기화라 함
      1. 프록시 객체에 member.getName() 호출해서 실제 데이터 조회
      1. 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청함 (초기화)
      1. 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체 생성
      1. 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관
      1. 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과 반환
  • 프록시의 특징 2
    • 처음 사용할 때 한 번만 초기화 됨
    • 초기화 한다고 실제 엔티티로 바뀌는 것이 아닌 프록시 객체를 통해서 실제 엔티티에 접근할 수 있는 것
    • 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시에 주의해서 사용 필요
    • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 디비를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환
    • 초기화는 영속성 컨텍스트의 도움을 받아야 가능
    • 따라서 영속성 컨텍스트에 도움을 받을 수 없는 준영속 상태의 프록시 객체를 초기화하면 문제 발생(org.hibernate.LazyInitializationException)

8.1.2 프록시와 식별자

  • 엔티티를 프록시로 조회할 때 식별자(PK) 값을 파라미터로 전달하는데 프록시 객체는 이 식별자 값을 보관
  • 프록시 객체는 식별자 값을 가지고 있으면 조회해도 프록시 초기화하지 않음 (단, 엔티티 접근 방식을 프로퍼티(@Access(AccessType.PROPERTY))로 설정한 경우에만 초기화하지 않음 -> 접근 방식 필드(@Access(AccessType.FIELD))로 했을때는 초기화 함)
  • 연관관계를 설정할 때는 식별자 값만 사용하므로 프록시를 사용하면 디비 접근 횟수를 줄일 수 있음. —>>>>> 접근 횟수
  • 참고로 연관관계 설정할 때는 엔티티 접근 방식을 필드로 설정해도 프록시를 초기화하지 않음 ->??

8.1.3 프록시 확인

  • JPA가 제공하는 PersistenceUnitUtil.isLoaded(Object entity) 메서드 사용 시 프록시 인스턴스의 초기화 여부 확인 가능 (초기화되지 않은 프록시 인스턴스는 false 반환)
  • 프록시 강제 초기화
    • 하이버네이트의 initialize() 사용시 강제 초기화 가능
    • JPA 표준에는 프록시 강제 초기화 메서드가 없음

8.2 즉시 로딩과 지연 로딩

  • 즉시 로딩
    • 엔티티를 조회할 때 연관된 엔티티도 함께 조회
    • 설정 방법 : FetchType.EAGER - 1 : 1
  • 지연 로딩
    • 연관된 엔티티를 실제 사용할 때 조회
    • 설정 방법 : FetchType.LAZY - 1 : N

8.2.1 즉시 로딩

  • 회원과 팀 테이블을 즉시 로딩으로 조회하면 각각의 두 테이블을 조회하는 것이 아닌 JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용함
  • NULL 제약 조건과 JPA 조인 전략 (참고)
    • 즉시 로딩 실행 SQL에서 JPA가 내부 조인(INNER JOIN)이 아닌 외부 조인(LEFT OUTER JOIN)을 사용한 것을 유심히 봐야함
    • 회원 테이블의 TEAM_ID 외래 키는 NULL 값을 허용하지 않음 -> 따라서 팀에 소속되지 않은 회원이 있을 가능성 있음 -> 팀에 소속하지 않은 회원과 팀을 내부 조인하면 팀은 물론이고 회원 데이터도 조회 불가
    • 이러한 상황을 고려해 외부 조인을 사용하는 것
    • 하지만 외부 조인보다 내부 조인이 성능과 최적화에서 더 유리
    • 내부 조인 사용 시 외래 키에 NOT NULL 제약 조건을 설정하면 값이 있는 것을 보장하게 되고 JPA에 코드로 @JoinColumn에 nullable = false 설정 시 내부 조인을 사용하게 됨

8.2.2 지연 로딩

  • 멤버를 조회하게 되면 팀은 조회하지 않고 조회한 회원의 team 멤버변수에 프록시 객체를 넣어둠
  • 해당 프록시 객체는 실제 사용될 때까지 데이터 로딩을 미룸
  • 참고로 조회 대상이 영속성 컨텍스트에 이미 있으면 프록시 객체를 사용할 이유가 없음
  • 따라서 프록시가 아닌 실제 객체를 사용함

8.2.3 즉시 로딩, 지연 로딩 정리

  • 지연 로딩 : 연관된 엔티티를 프록시로 조회한다. 프록시를 실제 사용할 때 초기화하면서 데이터베이스를 조회한다.
  • 즉시 로딩 : 연관된 엔티티를 즉시 조회, 하이버네이트는 가능하면 SQL 조인을 사용해서 한 번에 조회
  • 지연 로딩과 즉시 로딩 중 어느 것을 사용하면 좋은지는 상황에 따라 다름

댓글