Reference. 자바 ORM 표준 JPA 프로그래밍
책 목차 및 이전 글
더보기
들어가기 전 JPA 특징, Q&A
1. JPA 소개
3. 영속성 관리
4. 엔티티 매핑
4.1 - 4.3 @Entity, @Table, 다양한 매핑
4.4 - 4.5 데이터베이스 스키마 자동 생성, DDL 생성 기능
5. 연관관계 매핑 기초
6. 다양한 연관관계 매핑
7. 고급매핑
8.1 프록시
- 지연 로딩: 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법
- 프록시 객체: 실제 엔티티 객체 대신에 데이터베이스 조회를 지연하는 가짜 객체로 사용
8.1.1 프록시 기초
- JPA에서 엔티티 하나를 조회할 떄
EntityManager.find()
를 사용- 예) Member member = em.find(Member.class, "member1");
- 엔티티를 직접 조회하면 실제 사용하든 사용하지 않든 데이터베이스를 조회
- 실제 사용하는 시점까지 조회를 미루고 싶으면
EntityManager.getReference()
사용- 예) Member member = em.getReference(Member.class, "member1");
- 데이터베이스 조회하지 않고 객체도 생성하지 않음, 프록시 객체를 반환
프록시의 특징
- 프록시 클래스는 실제 클래스를 상속받아 만들어지므로 겉모양이 동일
- 사용하는 입장에선 진짜 객체인지 프록시 객채엔지 구분하지 않고 사용
- 프록시 객체는 실제 객체에 대한 참조(target)를 보관
- 프록시 객체의 메소드를 호출하면 실제 객체의 메소드를 호출
프록시 객체의 초기화
- 프록시 객체 초기화:
member.getName()
처럼 사용될 때 데이터베이스를 조회해서 엔티티 생성
프록시 초기화 예제
Member member = em.getReference(Member.class, "id1");
member.getName();
프록시의 초기화 과정
- 프록시 객체에
member.getName()
을 통해 실제 데이터 조회
- 실제 엔티티가 생성되지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청 (초기화)
- 영속성 컨텍스트가 데이터베이스를 조회해서 실제 엔티티 객체 생성
- 생성된 실제 엔티티 객체의 참조를
Member target
멤버 변수에 보관
- 실제 엔티티 객체의
getName()
으로 결과 반환
프록시의 특징
- 프록시 객체는 처음 사용할 떄 한 번만 초기화
- 초기화 후 프록시 객체가 실제 엔티티로 바뀌지 않음, 프록시 객체를 통해 실제 엔티티 접근 가능
- 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입체크시 주의해서 사용
- 영속성 컨텍스트에 엔티티가 존재하면 DB조회가 필요 없음
getReference()
호출해도 프록시가 아닌 실제 엔티티 반환
- 초기화는 영속성 컨텍스트의 도움을 받아야 가능 준영속 상태의 프록시를 초기화하면 문제가 발생 (
영속성 컨텍스트 관리 X
) 하이버네이트는org.hibernamte.LazyInitializationException
예외 발생준영속 상태 초기화 예시- 영속성 컨텍스트가 종료된 후 프록시를 초기화하면 예외 발생
Member member = em.getReference(Member.class, "id1"); transaction.commit(); em.close() //영속성 컨텍스트 종료 member.getName(); //준영속 상태 초기화 LazyInitializationException 예외 발생
8.1.2 프록시와 식별자
- 프록시 객체는 식별자(PK) 값을 보관
Team team = em.getReference(Team.class, "team1");
team.getId(); //초기화 되지 않음
- 엔티티 접근 방식을 프로퍼티(
@Access(AccessType.PROPERTY)
)로 설정한 경우만 초기화 X
- 엔티티 접근 방식을 필드(
@Access(AccessType.FIELD)
)로 설정하면 프록시 객체 초기화 O
- 프록시를 사용하면 데이터베이스 접근 횟수를 줄이는 장점
- 연관관계를 설정할 때는 엔티티 접근 방식을 필드로 설정해도 프록시 초기화 X
8.1.3 프록시 확인
PersistenceUnitUtil.isLoaded(Object entity)
메소드를 통해 프록시 초기화 여부 확인 가능- 초기화되지 않은 프록시는 false, 초기화되었거나 프록시 인스턴스가 아니면 true 반환
boolean isLoad = em.getEntityManagerFactory() .getPersistenceUnitUtil().isLoaded(entity);
- 조회한 엔티티가 진짜 엔티티인지 프록시로 조회한 것인지 확인하려면 클래스명을 직접 출력
- 클래스명 뒤에 ..javassist..라 되어있으면 프록시
- 프록시를 생성하는 라이브러리에 따라 출력결과는 달라짐
System.out.println("memberProxy = " + member.getClass().getName()); //결과: memberProxy = jpabook.domain.Member_$$_javassist_0
- 하이버네이트
initialize()
를 사용하면 프록시를 강제로 초기화 (JPA는 초기화 메소드 X)org.hibernate.Hibernate.initialize(order.getMember()); //프록시 초기화
8.2 즉시 로딩과 지연 로딩
- 프록시 객체는 주로 연관된 엔티티를 지연 로딩할 때 사용
- JPA는 연관된 엔티티의 조회 시점을 선택할 수 있도록 두가지 방법 제공
- 즉시 로딩: 엔티티를 조회 할 때 연관된 엔티티도 함께 조회
- 예)
em.find(Member.class, "member1")
호출하면 회원과 연관된 팀 엔티티도 함께 조회
- 설정 방법:
@ManyToOne(fetch = FetchType.EAGER)
- 예)
- 지연 로딩: 연관된 엔티티를 실제 사용할 떄 조회
- 예)
member.getTeam().getName()
처럼 실제 사용시점에서 SQL 호출해서 팀 엔티티 조회
- 설정 방법:
@ManyToOne(fetch = FetchType.LAZY)
- 예)
- 즉시 로딩: 엔티티를 조회 할 때 연관된 엔티티도 함께 조회
8.2.1 즉시 로딩
- 즉시 로딩을 사용하려면 fetch 속성을 FetchType.EAGER로 지정
즉시 로딩 설정 및 사용 예시
//즉시 로딩 설정
@Entity
public class Member {
...
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
//사용 예시
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); //객체 그래프 탐색
- 회원을 조회하는 순간 팀도 함께 조회 (즉시 로딩)
- 대부분의 JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리 사용
//실행된 쿼리 SELECT M.MEMBER_ID AS MEMBER_ID, M.TEAM_ID AS TEAM_ID, M.USERNAME AS USERNAME, T.TEAM_ID AS TEAM_ID, T.NAME AS NAME FROM MEMBER M LEFT OUTER JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID WHERE M.MEMBER_ID = 'member1'
NULL 제약조건과 JPA 조인 전략
- 회원 테이블에 TEAM_ID 외래 키는 NULL 값을 허용, 따라서 JPA는 외부 조인(OUTER)을 사용
- 회원은 팀에 소속되지 않은 회원이 있을 가능성이 존재 그런 경우 내부 조인하면 팀은 물론이고 회원 데이터도 조회할 수 없는 상황 발생
- 외부 조인보다 내부 조인이 성능과 최적화에서 유리 (내부 조인이 외부 조인보다 속도가 빠름)
- 외래 키에 NOT NULL 제약 조건을 설정하면 내부 조인만 사용해도 가능 (nullable 설정)
@JoinColumn(nullable = true)
: NULL 허용(기본값), 외부 조인 사용
@JoinColumn(nullable = false)
: NULL 허용 안함, 내부 조인 사용
//nullable 속성 사용 @Entity public class Member { ... @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "TEAM_ID", nullable = false) private Team team; ... } //@ManyToOne.optional 속성 사용 @Entity public class Member { ... @ManyToOne(fetch = FetchType.EAGER, optional = false) @JoinColumn(name = "TEAM_ID") private Team team; ... }
8.2.2 지연 로딩
- 지연 로딩을 사용하려면 @ManyToOne의 fetch 속성을 FetchType.LAZY로 지정
지연 로딩 설정 및 사용 예시
//즉시 로딩 설정
@Entity
public class Member {
...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
//사용 예시
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); //객체 그래프 탐색
team.getName(); //팀 객체 실제 사용
em.find(Member.class, "member1")
회원만 조회 team변수에는 프록시 객체로 대체
- 프록시 객체는 실제 사용될 때까지 데이터 로딩을 미룸, 그것이 지연로딩
//em.find(Member.class, "member1") 호출시 SELECT * FROM MEMBER WHERE MEMBER_ID = 'member1' //team.getName() 호출시 SELECT * FROM TEAM WHERE TEAM_ID = 'team1'
- 조회 대상이 영속성 컨텍스트에 이미 있으면 프록시가 아닌 실제 객체를 사용
'개발서적 > 자바 ORM 표준 JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 8.4~5 영속성 전이, 고아 객체 (0) | 2021.08.25 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 8.3 지연 로딩 활용 (0) | 2021.08.25 |
[자바 ORM 표준 JPA 프로그래밍] 7.5 엔티티 하나에 여러 테이블 매핑 (0) | 2021.08.18 |
[자바 ORM 표준 JPA 프로그래밍] 7.4 조인 테이블 (0) | 2021.08.18 |
[자바 ORM 표준 JPA 프로그래밍] 7.3 복합 키와 식별 관계 매핑 (0) | 2021.08.18 |