지연 로딩 기능을 사용하려면 실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요 → 이를 프록시라 함
프록시 기초
엔티티를 직접 조회할 때는 em.find 사용
조회한 엔티티를 실제 사용하든 사용하지 않든 데이터베이스를 조회
엔티티를 실제 사용하는 시점까지 데이터베이스 조회를 미루려면 em.getReference 사용
데이터베이스 접근을 위임한 프록시 객체를 반환
실제 클래스와 겉 모양이 같으므로 사용하는 입장에서는 구분 없이 사용
프록시는 실체 객체의 참조를 보관
프록시 객체드의 메서드 호출 → 실제 객체의 메서드 호출
프록시 객체는 실제 사용될 때 데이터베이스를 조회해 실제 엔티티 객체 생성
메서드 호출(실제 사용)
→ 영속성 컨텍스트로 초기화 요청
→ 영속성 컨텍스트가 DB 조회
→ 영속성 컨텍스트에서 실제 엔티티 생성
→ 프록시에서 실제 엔티티의 메서드 호출
프록시의 특징
프록시 객체는 처음 사용할 때 한 번만 초기화
프록시 객체를 초기화한다고 실제 엔티티로 바뀌는 것은 아님
프록시 객체를 통해 실제 엔티티에 접근
프록시 객체는 원본 엔티티를 상속받은 객체 → 타입 체크 시에 주의
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference를 호출해도 실제 엔티티 반환
초기화는 영속성 컨텍스트의 도움을 받아야 가능하므로 준영속 상태의 프록시 초기화 불가능
LazyInitializationException 발생(하이버네이트)
프록시 객체는 식별자 값을 가짐
식별자 값을 조회하는 메서드는 호출해도 프록시를 초기화 하지 않음
단, 엔티티 접근 방식이 @Access(AccessType.PROPERTY))일 경우만
연관관계 설정 시 식별자 값만 사용하므로 프록시 객체를 매핑하면 데이터베이스 접근 횟수를 줄일 수 있음
PersistenceUnitUtil.isLoaded 메서드로 프록시 인스턴스의 초기화 여부를 알 수 있음
초기화되지 않으면 false
즉시 로딩과 지연 로딩
즉시 로딩
엔티티를 조회할 때 연관된 엔티티도 함께 조회
@ManyToOne(fetch = FetchType.EAGER)
쿼리를 2번 실행하지 않고 즉시 로딩을 최적화 하기 위해 가능하면 조인 쿼리 사용
외래 키에 NOT NULL 제약 조건이 없으면 OUTER JOIN
외래 키에 NOT NULL 제약 조건이 걸려 있으면 INNER JOIN
이 때, NOT NULL을 걸었으면 @JoinColumn에 nullable = false를 설정해야 함
지연 로딩
연관된 엔티티를 실제 사용할 때 조회
조회한 엔티티를 실제 사용하는 시점에 SQL을 호출해서 조회
@ManyToOne(fetch = FetchType.LAZY)
연관관계로 맺어진 멤버변수에 프록시 객체가 들어감
실제 사용 시점에 데이터베이스를 조회해서 초기화
조인 대신 쿼리가 두 번 사용됨
지연 로딩 활용
사내 주문 관리 시스템 설계
회원(Member)은 팀(Team) 하나에만 소속할 수 있음 (N:1)
회원(Member)은 여러 주문내역(Order)을 가짐 (1:N)
주문내역(Order)은 상품정보(Product)를 가짐 (N:1)
애플리케이션 로직 분석
Member와 연관된 Team은 자주 함께 사용됨
Member와 Team은 즉시 로딩
Member와 연관된 Order는 가끔 사용됨
Member와 Order는 지연 로딩
Order와 연관된 Product는 자주 함께 사용
Order와 Product는 즉시 로딩
프록시와 컬렉션 래퍼
하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티에 컬렉션이 있으면 컬렉션을 추적하고 관리할 목적으로 원본 컬렉션을 하이버네이트가 제공하는 내장 컬렉션으로 변경
이를 컬렉션 래퍼라 함
엔티티 지연 로딩 → 프록시 객체 사용
컬렉션 지연 로딩 → 컬렉션 래퍼가 처리
컬렉션 래퍼도 프록시 역할을 하므로 프록시라고 부름
컬렉션은 컬렉션을 호출할 때가 아니라 컬렉션에서 실제 데이터를 조회할 때 초기화
JPA 기본 페치 전략
@ManyToOne, @OneToOne: 즉시 로딩(FetchType.EAGER)
@OneToMany, @ManyToMany: 지연 로딩(FetchType.LAZY)
연관된 엔티티가 하나면 즉시 로딩, 컬렉션이면 지연 로딩이 기본
컬렉션 로딩은 비용이 많이 들고 잘못 사용하면 너무 많은 데이터를 로딩
모든 연관관계에 지연 로딩을 사용하는 것을 추천
실제 사용하는 상황을 보고 꼭 필요한 곳에만 즉시 로딩을 사용하도록 최적화 추천
SQL과는 다른 유연한 최적화 가능
컬렉션에 FetchType.EAGER 사용 시 주의점
컬렉션을 하나 이상 두 개 이상 즉시 로딩하는 것은 권장 x
컬렉션과 조인 == 일대다 조인 이므로 결과 데이터가 다 쪽에 있는 수만큼 증가
A 테이블이 N, M 두 테이블과 일대다 조인하면 SQL 실행 결과가 N x M이 됨
성능 저하
컬렉션 즉시 로딩은 항상 외부 조인 사용
팀 → 회원으로 일대다 관계를 조인할 때 회원이 한 명도 없는 팀을 내부 조인하면 팀까지 조회되지 않는 문제 발생
따라서 즉시 로딩 시 항상 외부 조인 사용
정리
@ManyToOne, @OneToOne
(optional = false): 내부 조인
(optional = true): 외부 조인
@OneToMany, @ManyToMany
(optional = false): 외부 조인
(optional = true): 외부 조인
영속성 전이: CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함게 영속상태로 만드는 기능
JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 함
영속성 전이를 사용하면 부모만 영속 상태로 만들어도 연관된 자식까지 한 번에 영속 상태로 만들 수 있음
영속성 전이: 저장
매핑 어노테이션에 cascade = CascadeType.PERSIST 옵션 설정
@Entity
public class Parent {
...
@oneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<Child>();
...
}
연관관계 추가 후 부모 엔티티 저장하면 자식 엔티티 저장됨
영속성 전이와 연관관계 매핑은 아무 관련이 없으므로 연관관계 매핑이 반드시 필요
영속성 전이: 삭제
영속성 전이를 CascadeType.REMOVE로 설정하면 영속성 전이를 엔티티를 삭제할 때도 사용 가능
CascadeType.PERSIST, CascadeType.REMOVE는 em.persist, em.remove를 실행할 때가 아닌 플러시를 호출할 때 전이가 발생
고아 객체
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능
부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<>();
...
}
orphanRemoval = true 설정으로 고아 객체 제거 기능 사용 설정
컬렉션에서 제거된 엔티티는 자동으로 삭제됨
고아 객체 제거 기능은 영속성 컨텍스트를 플러시할 때 적용
참조가 제거된 엔티티를 다른 곳에서 참조하지 않는 고아 객체로 보고 사용하는 기능
@OneToOne, @OneToMany 에만 사용 가능
부모를 제거하면 자식은 고아가 되므로 자식도 제거됨
CascadeType.REMOVE를 설정한 것과 같음
영속성 전이 + 고아 객체, 생명주기
CascadeType.ALL + orphanRemoval = true를 동시에 사용할 경우
섹션 8. 프록시와 연관관계 관리
프록시
프록시 기초
em.find
사용em.getReference
사용프록시 객체는 실제 사용될 때 데이터베이스를 조회해 실제 엔티티 객체 생성
메서드 호출(실제 사용)
→ 영속성 컨텍스트로 초기화 요청
→ 영속성 컨텍스트가 DB 조회
→ 영속성 컨텍스트에서 실제 엔티티 생성
→ 프록시에서 실제 엔티티의 메서드 호출
프록시의 특징
em.getReference
를 호출해도 실제 엔티티 반환LazyInitializationException
발생(하이버네이트)@Access(AccessType.PROPERTY))
일 경우만PersistenceUnitUtil.isLoaded
메서드로 프록시 인스턴스의 초기화 여부를 알 수 있음즉시 로딩과 지연 로딩
즉시 로딩
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn
에nullable = false
를 설정해야 함지연 로딩
@ManyToOne(fetch = FetchType.LAZY)
지연 로딩 활용
사내 주문 관리 시스템 설계
Member
)은 팀(Team
) 하나에만 소속할 수 있음 (N:1)Member
)은 여러 주문내역(Order
)을 가짐 (1:N)Order
)은 상품정보(Product
)를 가짐 (N:1)애플리케이션 로직 분석
Member
와 연관된Team
은 자주 함께 사용됨Member
와Team
은 즉시 로딩Member
와 연관된Order
는 가끔 사용됨Member
와Order
는 지연 로딩Order
와 연관된Product
는 자주 함께 사용Order
와Product
는 즉시 로딩프록시와 컬렉션 래퍼
하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티에 컬렉션이 있으면 컬렉션을 추적하고 관리할 목적으로 원본 컬렉션을 하이버네이트가 제공하는 내장 컬렉션으로 변경
JPA 기본 페치 전략
@ManyToOne
,@OneToOne
: 즉시 로딩(FetchType.EAGER
)@OneToMany
,@ManyToMany
: 지연 로딩(FetchType.LAZY
)컬렉션에
FetchType.EAGER
사용 시 주의점하나 이상두 개 이상 즉시 로딩하는 것은 권장 x@ManyToOne
,@OneToOne
@OneToMany
,@ManyToMany
영속성 전이: CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함게 영속상태로 만드는 기능
영속성 전이: 저장
cascade = CascadeType.PERSIST
옵션 설정영속성 전이: 삭제
CascadeType.REMOVE
로 설정하면 영속성 전이를 엔티티를 삭제할 때도 사용 가능DELETE
쿼리를 3번 날리고 부모 및 연관된 자식을 모두 삭제CASCADE의 종류
cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
CascadeType.PERSIST
,CascadeType.REMOVE
는em.persist
,em.remove
를 실행할 때가 아닌 플러시를 호출할 때 전이가 발생고아 객체
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능
orphanRemoval = true
설정으로 고아 객체 제거 기능 사용 설정@OneToOne
,@OneToMany
에만 사용 가능CascadeType.REMOVE
를 설정한 것과 같음영속성 전이 + 고아 객체, 생명주기
CascadeType.ALL
+orphanRemoval = true
를 동시에 사용할 경우