JPA 프록시

프록시

  • entityManager.find() : DB에서 엔티티 객체 조회
  • entityManager.getReference() : 데이터베이스 조회를 미루는 가짜 엔티티 객체 조회

특징

  • 실제 클래스를 상속 받아서 만들어짐
  • 실제 클래스와 겉 모양이 같다.
  • 사용시 진짜 객체인지 프록시 객체인지 구분하지 않고 사용 (이론상)
  • 프록시 객체는 실제 객체의 참조(target)을 보관
프록시 객체의 초기화

image

  1. 프록시 객체는 처음 사용할 때 한번만 초기화
  2. 프록시 객체가 실제 엔티티로 바뀌는것이 아님, 실제 엔티티에 접근 가능한것
  3. 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의 (== 비교 실패 , 대신 instance of 사용)
  4. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면, entityManager.getReference()를 호출 시 실제 엔티티 반환
  5. 영속성 컨텍스트의 도움을 받을 수 없는 준영속 일경우 프록시를 초기화문제 발생

프록시 확인

  • 프록시 인스턴스 초기화 여부 확인 (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로 설정해야함