JPA 표준명세는 지연로딩의 구현방법을 JPA 구현체에 위임
1. 기본개념
em.find(): 실제 엔티티 객체 조회(식별자를 사용)
em.getReference(): DB 조회를 미루는 가짜(프록시) 엔티티 객체 조회(쿼리 X)
MemberMapping member = new MemberMapping();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
MemberMapping findMember = em.find(MemberMapping.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getUsername());
tx.commit();
MemberMapping member = new MemberMapping();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
MemberMapping findMember = em.getReference(MemberMapping.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
//getId는 파라미터로 받은 값(DB에 있는 값이 아니라)
tx.commit();
MemberMapping member = new MemberMapping();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
MemberMapping findMember = em.getReference(MemberMapping.class, member.getId());
System.out.println("findMember.class = " + findMember.getClass());
System.out.println("findMember.username = " + findMember.getUsername());
tx.commit();
findMember.class = class hellojpa.mapping.MemberMapping$HibernateProxy$3nz5GHdh
2. 프록시의 특징
(1) 실제 클래스를 상속받아서 겉모양이 동일하게 생성됨
(2) 프록시 객체는 실제 객체의 참조(target)를 보관
(3) 프록시 객체가 호출되면, 프록시 객체는 실제 객체의 메소드 호출
📌프록시 객체의 초기화
Member member = em.getReference(Member.class, “id1”);
member.getUsername();
1) 프록시 객체는 처음 사용할 때 한 번만 초기화
2) 초기화할 때 프록시 객체가 실제 엔티티로 바뀌는 것이 X
- 내부에 참조 값이 채워지는 것일 뿐, 여전히 proxy
3) 초기화가 되면 프록시 객체가 실제 엔티티에 접근 가능한 것
4) 프록시 객체는 원본 엔티티를 상속받음(hibernate가 자동으로)
- 따라서 타입 체크시 주의!!!
- == 비교 X, 대신 instance of 사용
📌 JPA에서는 프록시 객체가 사용될 경우가 있음을 염두, 가급적 instance of 사용하여 비교
MemberMapping member1 = new MemberMapping();
member1.setUsername("hello");
em.persist(member1);
em.flush();
em.clear();
MemberMapping findMember = em.find(MemberMapping.class, member1.getId());
System.out.println("before findMember = " + findMember.getClass()); //proxy
System.out.println("findMember.username = " + findMember.getUsername()); //select쿼리 실행
System.out.println("after findMember = " + findMember.getClass()); //여전히 proxy
MemberMapping member1 = new MemberMapping();
member1.setUsername("member1");
em.persist(member1);
MemberMapping member2 = new MemberMapping();
member2.setUsername("member2");
em.persist(member2);
em.flush();
em.clear();
MemberMapping findMember1 = em.find(MemberMapping.class, member1.getId());
MemberMapping findMember2 = em.find(MemberMapping.class, member2.getId());
System.out.println("findMember1 == findMember2 " + (findMember1.getClass() == findMember2.getClass())); //true
MemberMapping member1 = new MemberMapping();
member1.setUsername("member1");
em.persist(member1);
MemberMapping member2 = new MemberMapping();
member2.setUsername("member2");
em.persist(member2);
em.flush();
em.clear();
MemberMapping findMember1 = em.find(MemberMapping.class, member1.getId());
MemberMapping findMember2 = em.getReference(MemberMapping.class, member2.getId());
System.out.println("findMember1 == findMember2 " + (findMember1.getClass() == findMember2.getClass())); //false
System.out.println("findMember1 == findMember2 " + (findMember1 instance of Member)); //true
System.out.println("findMember1 == findMember2 " + (findMember2 instance of Member)); //true
📌 같은 영속성 컨텍스트 안(식별자가 동일)에서는 동일성을 보장하기 위한 JPA의 원칙(JPA의 메커니즘)!!!
//case1: find(실제 엔티티) -> reference(실제 엔티티)
MemberMapping member1 = new MemberMapping();
member1.setUsername("hello");
em.persist(member1);
em.flush();
em.clear();
MemberMapping findMember1 = em.find(MemberMapping.class, member1.getId()); //영속성 컨텍스트에 올라감
System.out.println("findMember1 = " + findMember1.getClass()); //MemberMapping
MemberMapping reference1 = em.getReference(MemberMapping.class, member1.getId());
System.out.println("reference1 = " + reference1.getClass()); //MemberMapping
System.out.println("a == a " + (findMember1 == reference1)); //true
//같은 영속성 컨텍스트 안에 있다면 JPA는 늘 같은 객체를 반환(동일성 보장 원칙)
//이러한 원칙을 지키기 위해서라도 영속성 컨텍스트 안에 이미 값이 있다면 프록시가 아닌 실제 엔티티를 반환
tx.commit();
//case2: reference(프록시) -> reference(프록시)
MemberMapping member1 = new MemberMapping();
member1.setUsername("hello");
em.persist(member1);
em.flush();
em.clear();
MemberMapping reference1 = em.getReference(MemberMapping.class, member1.getId());
System.out.println("reference1 = " + reference1.getClass()); //proxy
MemberMapping reference2 = em.getReference(MemberMapping.class, member1.getId());
System.out.println("reference2= " + reference2.getClass()); //proxy(전자와 동일한 proxy)
System.out.println("a == a " + (reference1 == reference2)); //true
tx.commit();
//case3: reference(프록시) -> find(프록시)
MemberMapping member1 = new MemberMapping();
member1.setUsername("hello");
em.persist(member1);
em.flush();
em.clear();
MemberMapping reference1 = em.getReference(MemberMapping.class, member1.getId());
System.out.println("reference1 = " + reference1.getClass()); //proxy
MemberMapping findMember1 = em.find(MemberMapping.class, member1.getId());
System.out.println("findMember1= " + findMember1.getClass()); //proxy(동일성 보장 때문에 앞서 가져온 proxy를 반환)
System.out.println("a == a " + (reference1 == findMember1)); //true
tx.commit();
실무에서 많이 맞닥뜨리게 되는 에러!!!
(ex. 트랜잭션 종료 후 프록시 객체 조회하는 경우 등)
MemberMapping member1 = new MemberMapping();
member1.setUsername("hello");
em.persist(member1);
em.flush();
em.clear();
MemberMapping refMember = em.getReference(MemberMapping.class, member1.getId()); //영속성 컨텍스트에 올라감
System.out.println("refMember = " + refMember.getClass()); //proxy
em.detach(refMember); //영속성 컨텍스트에서 분리
refMember.getUsername(); //프록시 초기화 불가
tx.commit();
3. 프록시의 확인
MemberMapping member1 = new MemberMapping();
member1.setUsername("hello");
em.persist(member1);
em.flush();
em.clear();
MemberMapping refMember = em.getReference(MemberMapping.class, member1.getId()); //영속성 컨텍스트에 올라감
System.out.println("refMember = " + refMember.getClass()); //proxy
System.out.println("isLoaded1 = " + emf.getPersistenceUnitUtil().isLoaded(refMember)); //false
refMember.getUsername();
System.out.println("isLoaded2 = " + emf.getPersistenceUnitUtil().isLoaded(refMember)); //true
tx.commit();
System.out.println("refMember = " + refMember.getClass()); //xxx$HibernateProxy$xxx
//refMember.getUsername(); //방법1. 사실상 강제 초기화
Hibernate.initialize(refMember); //방법2. 하이버네이트가 제공하는 강제 초기화 메소드