JPA 프록시
프록시
- entityManager.find() : DB에서 엔티티 객체 조회
- entityManager.getReference() : 데이터베이스 조회를 미루는 가짜 엔티티 객체 조회
특징
- 실제 클래스를 상속 받아서 만들어짐
- 실제 클래스와 겉 모양이 같다.
- 사용시 진짜 객체인지 프록시 객체인지 구분하지 않고 사용 (이론상)
- 프록시 객체는 실제 객체의 참조(target)을 보관
프록시 객체의 초기화
- 프록시 객체는 처음 사용할 때 한번만 초기화
- 프록시 객체가 실제 엔티티로 바뀌는것이 아님, 실제 엔티티에 접근 가능한것
- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의 (== 비교 실패 , 대신 instance of 사용)
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면, entityManager.getReference()를 호출 시 실제 엔티티 반환
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 일경우 프록시를 초기화문제 발생
프록시 확인
- 프록시 인스턴스 초기화 여부 확인 (
PersistenceUnitUtil.isLoaded(Object entity)
)(entity Manage Factory에서 불러옴) - 프록시 클래스 확인 방법 (
entity.getClass()
) - 강제 초기화 (
Hibernate.initialize()
)
지연 로딩
- 매핑 어노테이션에 fetch 타입을 LAZY로 설정
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();
Member refMember = em.find(Member.class, member1.getId());
//얘는 프록시로 우선 찍힘
System.out.println("refMember.getTeam().getClass() = " + refMember.getTeam().getClass());
System.out.println("===================");
refMember.getTeam().getName(); // 실제 팀의 !!내용!!을 가져옴
System.out.println("===================");
refMember.getTeam().getClass()
는 프록시로 가져온다.- 우선 멤버만 find하고 팀은 프록시로 가져온다.
- 이후 만약 팀의 내용을 가지고 온다면 그때 Team 테이블에서 조회해온다.
Hibernate:
select
member0_.id as id1_3_0_,
member0_.createBy as createBy2_3_0_,
member0_.createdDate as createdD3_3_0_,
member0_.lastModifiedBy as lastModi4_3_0_,
member0_.lastModifiedDate as lastModi5_3_0_,
member0_.age as age6_3_0_,
member0_.createDate as createDa7_3_0_,
member0_.description as descript8_3_0_,
member0_.localDate as localDat9_3_0_,
member0_.localDateTime as localDa10_3_0_,
member0_.roleType as roleTyp11_3_0_,
member0_.TEAM_ID as TEAM_ID13_3_0_,
member0_.name as name12_3_0_
from
Member member0_
where
member0_.id=?
refMember.getTeam().getClass() = class hellojpa.Team$HibernateProxy$BspWmUMG
===================
Hibernate:
select
team0_.TEAM_ID as TEAM_ID1_5_0_,
team0_.name as name2_5_0_
from
Team team0_
where
team0_.TEAM_ID=?
===================
즉시 로딩
멤버와 팀을 자주 같이 사용한다면?
- 즉시 로딩을 사용
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
- 멤버와 팀을 애초에 쿼리에서 조인해서 전부 가져옴
- 프록시가 필요가 없음
Hibernate:
select
member0_.id as id1_3_0_,
member0_.createBy as createBy2_3_0_,
member0_.createdDate as createdD3_3_0_,
member0_.lastModifiedBy as lastModi4_3_0_,
member0_.lastModifiedDate as lastModi5_3_0_,
member0_.age as age6_3_0_,
member0_.createDate as createDa7_3_0_,
member0_.description as descript8_3_0_,
member0_.localDate as localDat9_3_0_,
member0_.localDateTime as localDa10_3_0_,
member0_.roleType as roleTyp11_3_0_,
member0_.TEAM_ID as TEAM_ID13_3_0_,
member0_.name as name12_3_0_,
team1_.TEAM_ID as TEAM_ID1_5_1_,
team1_.name as name2_5_1_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.id=?
refMember.getTeam().getClass() = class hellojpa.Team
===================
===================
주의
- 실무에서 가급적 지연로딩 사용
- 즉시 로딩 적용시 예상치 못한 SQL 발생
- 즉시 로딩은 JPQL에서 N+1 문제 발생
@MayToOne
,@OneToOne
은 기본이 즉시 로딩 -> LAZY로 설정해야함