1. 프록시
- 지연로딩: 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법
➡️ 프록시 객체: 실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체
특징
- 프록시 클래스는 실제 클래스를 상속 받아서 만들어지므로 실제 클래스와 겉모양이 같음
➡️ 타입 체크 시에 주의해서 사용
- 프록시 객체는 실제 객체에 대한 참조 보관
- 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출함
- 프록시 객체는 처음 사용할 때 한 번만 초기화됨
- 초기화한다고 프록시 객체가 실제 엔티티로 바귀는 것이 아니라 프록시 객체를 통해서 실제 엔티티에 접근할 수 있게 되는 것
- 영속성 컨텍스트에 탖는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티 반환
- 준영속 상태의 프록시를 초기화 하면 문제 발생,
하이버네이트는 org.hibernate.LazyInitializationException 예외 발생
프록시 객체의 초기화
- 프록시 객체의 초기화: 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성
Member member = em.getReference(Member.clss, "id1");
member.getName();
class MemberProxy extends Member {
Member target = null;
public String getName() {
if (target == null) {
this.target = ...;
}
return target.getName();
}
}
- 프록시 객체에 member.getName()을 호출해서 실제 데이터 조회
- 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성 요청
➡️ 초기화
- 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체 생성
- 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관
- 프록시 객체는 실제 엔티티 객체의 getName() 호출해서 결과 반환
프록시와 식별자
- 엔티티를 프록시로 조회할 때 식별자(PK) 값을 파라미터로 전달하는데 프록시 객체는 이 식별자 값을 보관
Team team = em.getReference(Team.class, "team1");
team.getId();
- 프록시 객체는 식별자 값을 가지고 있으므로 식별자 값을 조회하는 team.getId()를 호출해도 프록시 초기화X
- 엔티티 접근 방식을 프로퍼티(@Access(AccessType.PROPERTY))로 설정한 경우에만 초기화X
- 필드(@Access(AccessType.FIELD))로 설정하면 getId() 메소드가 id만 조회하는 메소드인지 다른 필드까지 활용해서 어떤 일을 하는 메소드인지 알지 못하므로 프록시 객체 초기화
- 연관관계를 설정할 대느 ㄴ엔티티 접근 방식을 필드로 설정해도 프록시를 초기화하지 않음
프록시 확인
- PersistenceUnitUtil.isLoaded(Object entity) 메소드를 사용하면 프록시 인스턴스의 초기화 여부를 확인할 수 있음
2. 즉시 로딩과 지연 로딩
- 즉시 로딩: 엔티티를 조회할 때 연관된 엔티티도 함께 조회
- 지연 로딩: 연관된 엔티티를 실제 사용할 때 조회
즉시 로딩
- fetch 속성: FetchType.EAGER
- 대부분의 JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리 사용
- 연관된 엔티티 즉시 조회
- NULL 제약조건과 JPA 조인 전략
- @JoinColumn(nullable = true): NULL 허용(기본값), 외부 조인 사용
- @JoinColumn(nullable = false): NULL 허용하지 않음, 내부 조인 사용
- JPA는 선택적 관계면 외부 조인을 사용하고 필수 관계면 내부 조인 사용
지연 로딩
- fetch 속성: FetchType.LAZY
- 연관된 엔티티를 프록시로 조회
- 프록시를 실제 사용할 대 초기화하면서 데이터베이스 조회
3. 지연 로딩 활용
프록시와 컬렉션 래퍼
- 하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티에 컬렉션이 있으면 컬렉션을 추적하고 관리할 목적으로 원본 컬렉션을 하이버네이트가 제공하는 내장 컬렉션으로 변경
➡️ 컬렉션 래퍼(org.hibernate.collection.internal.PersistentBag)
- 컬렉션은 컬렉션 래퍼가 지연 로딩 처리
- 컬렉션 래퍼도 컬렉션에 대한 프록시 역할
JPA 기본 페치 전략
- fetch 속성의 기본 설정값
- @ManyToOne, @OneToOne: 즉시 로딩(FetchType.EAGER)
- @OneToMany, @ManyToMany: 지연 로딩(FetchType.LAZY)
- 모든 연관관계에 지연 로딩 사용 추천
컬렉션에 FetchType.EAGER 사용 시 주의점
- 컬렉션을 하나 이상 즉시 로딩하는 것은 권장X
- 컬렉션 즉시 로딩은 항상 외부 조인(OUTER JOIN) 사용
- FetchType.EAGER 설정과 조인 전략 정리
- @ManyToOne, @OneToOne
- (optional = false): 내부 조인
- (optional = true): 외부 조인
- @OneToMany, @ManyToMany
- (optional = false): 외부 조인
- (optional = true): 외부 조인
4. 영속성 전이: CASCADE
영속성 전이: 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함게 영속 상태로 만드는 것
- CASCADE 옵션
public enum CascadeType {
ALL,
PERSIST,
MERGE,
REMOVE,
REFRESH,
DETACH
}
- JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 함
- 영속성 전이는 단지 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함 제공하는 것
- CascadeType.PERSIST, CascadeType.REMOVE는 em.persist(), em.remove()를 실행할 때 바로 전이가 발생하지 않고 플러시를 호출할 때 전이가 발생
5. 고아 객체
- 고아 객체 제거: 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
- 고아 객체 제거는 참조하는 곳이 하나일 때만 사용해야 함
- orphanRemoval은 @OneToOne, @OneToMany에만 사용 가능
- 부모를 제거하면 자식도 같이 제거됨 ➡️ CascadeType.REMOVE 설정한 것과 같음
6. 영속성 전이 + 고아 객체, 생명주기
- CascadeType.ALL + orphanRemoval = true
➡️ 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기 관리할 수 있음