프록시 객체는 처음 사용할 때 한 번만 초기화 된다.
프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있다.
프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시 주의해서 사용해야 한다.
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.
초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다.(예외)
엔티티 접근 방식을 프로퍼티로 접근한 경우에
프록시 객체는 식별자 값을 가지고 있으므로 getId()를 호출해도 프록시를 초기화 하지 않는다.
엔티티 전체가 AccessType.FIELD로 설정되어 있어도,
연관관계 필드가 Lazy(지연 로딩)일 경우,
단순히 연관관계 필드를 참조만 해도 초기화되진 않음
@joincolumn (nullable=false) 를 설정해서 이 외래 키는 NULL 값을 허용하지 않는다고 알려주면 JPA는 내부조인을 사용한다.
또는 @ManyToOne.optional = false로 설정해도 내부 조인을 사용한다.
| 항목 | 설명 |
|---|---|
| 결과 셋이 작음 | INNER JOIN은 양쪽에 매칭되는 데이터만 반환 → 결과가 더 작아짐 |
| 필터 조건 명확 | 불필요한 NULL 처리나 조건 분기 없음 |
| 인덱스 사용 최적화 | JOIN 조건에 걸리는 컬럼이 인덱스면, DB 옵티마이저가 효율적으로 조회 가능 |
| 쿼리 계획이 단순 | OUTER JOIN은 누락된 데이터 보존을 위해 복잡한 plan 필요 → cost 증가 |
| NULL 계산 없음 | OUTER JOIN은 JOIN 이후 NULL 필드에 대해 추가 연산 발생 가능 (ex. IFNULL, CASE 등) |
하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티에 컬렉션이 있으면 컬렉션을 추적하고 관리할 목적으로 원본 컬렉션을 하이버네이트 내장 컬렉션으로 변경하는데 이것을 컬렉션 래퍼라고 한다.
컬렉션 래퍼 또한 컬렉션에 대한 프록시 역할을 한다.
jpa의 기본 fetch 전략은
연관된 엔티티가 하나면 즉시 로딩,
컬렉션이면 지연 로딩을 사용한다.
@ManyToOne, @OneToOne: 즉시 로딩
@OneToMany, @ManyToMany: 지연 로딩
처음엔 모든 연관관계에 지연 로딩을 사용하고
꼭 필요한 곳에만 즉시 로딩을 사용하도록 최적화 하는 것이 좋음.
(다만 jpa가 아닌 sql문은 유연한 최적화가 어려움)
fetchType.eager 사용 시 주의점
컬렉션을 하나 이상 즉시 로딩하는 것은 x
컬렉션 즉시 로딩은 항상 외부조인을 사용한다
FetchType.EAGER 설정과 조인 전략 정리
@ManyToOne, @OneToOne
(Optional = false): 내부 조인
(Optional = true) : 외부 조인
@OneToMany, @ManyToMany
(Optional = false): 외부 조인
(Optional = true) : 내부 조인
jpa 구현체들은 객체 그래프를 마음껏 탐색할 수 있도록 지원하는 데 이때 프록시 기술을 사용한다