Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름 : " + member.getUsername());
System.out.println("소속팀 : " + team.getName());
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름 : " + member.getUsername());
회원만 출력하면 되는데 회원과 팀이 연관관계가 맺어져 있다고 팀 정보까지 DB에서 가져오면 불필요한 정보까지 가져오게 되는 거라 성능에 최적화에 문제가 생긴다.
JPA는 지연로딩이라는 것을 이용해 이러한 문제를 방지한다.
- em.find() vs em.getReference()
- em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
- em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 객체 조회
final Member member = new Member();
member.setName("hello");
em.persist(member);
em.flush();
em.clear();
final Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.getClass() = " + findMember.getClass());
System.out.println("findMember.getId() = " + findMember.getId());
System.out.println("findMember.getName() = " + findMember.getName());
em.find()를 호출 하는 시점에 DB에 쿼리를 조회해서 값을 가져온다.
또한 getClass() 메소드를 통해 실제 회원 엔티티를 가져오는 것을 알 수 있다.
final Member member = new Member();
member.setName("hello");
em.persist(member);
em.flush();
em.clear();
final Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.getClass() = " + findMember.getClass());
System.out.println("findMember.getId() = " + findMember.getId());
System.out.println("findMember.getName() = " + findMember.getName());
getReference()를 호출 하는 시점에는 DB에 쿼리를 조회하지 않는다.
그리고 나서 회원에 대한 정보가 필요할 때 그 때 DB에 쿼리를 조회해서 값을 가져오는 것을 확인할 수 있다.
또한 getClass() 메소드를 통해 실제 객체가 아닌 프록시 객체를 가져오는 것을 알 수 있다.
- 실제 클래스를 상속 받아서 만들어진다.
- 실제 클래스와 겉 모양이 같다.
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
- 프록시 객체는 실제 객체의 참조(target)를 보관한다.
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
- 회원 프록시 객체에서 getName() 메소드를 호출하면 프록시 객체는 실제 객체의 getName()메소드를 호출해야 하는데 아직 실제 객체가 어떤 것인지 알지 못하는 상태이다. (target = null)
- 따라서 영속성 컨텍스트에 실제 객체가 누구인지 요청하면 영속성 컨텍스트는 DB에서 실제 객체를 조회해 실제 객체를 생성한다.
- 프록시 객체에서 target을 실제객체로 초기화 한 후 (target = member)에target.getName()을 통해 실제 객체의 getName() 메소드를 호출한다.
- 프록시 객체는 처음 사용할 때 한번만 초기화한다.
- 프록시 객체를 초기화 할 때 프록시 객체가 실제 엔티티로 바뀌는 것이 아니라 초기화되면 프록시 객체를 통해 실제 엔티티에 접근이 가능하게 되는 것이다.
- 프록시 객체는 원본 엔티티를 상속받는다. 따라서 타입 체크시 주의해야 한다.(== 비교 실패, 대신에 instance of 사용)
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다. (a == a : 항상 true)
- JPA에서는 같은 트랜잭션(영속성 컨텍스트) 레벨안에서 같은 인스턴스를 조회하면 항상 같다고 나와야 한다.
- 때문에 em.getReference()로 프록시를 조회 한 후 em.find()로 실제 객체를 조회해도 JPA에서는 ==을 맞추기 위해 실제 객체가 아닌 프록시 객체를 반환한다.
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때 프록시를 초기화하면 문제가 발생한다.(하이버네이트는 LazyInitializationException 예외를 터트린다.)
final Member member = new Member();
member.setName("hello");
em.persist(member);
em.flush();
em.clear();
final Member reference = em.getReference(Member.class, member.getId());
System.out.println("reference.getClass() = " + reference.getClass());
// 1. em.detach(reference);
// 2. em.close();
System.out.println("reference.getName() = " + reference.getName());
reference.getName() 메소드를 호출하면 영속성 컨텍스트의 도움을 받아 실제 값을 가져와야 하는데 detach(reference)로 영속성 컨텍스트가 관리하지 않는 준영속 상태가 되면 값을 가져오지 못하기 때문에 값을 초기화할 수 없는 상태가 된다.
마찬가지로 em.close()로 영속성 컨텍스트를 닫아버리면 실제 값을 가져오지 못하기 때문에 값을 초기화할 수 없게 된다.
- 프록시 인스턴스의 초기화 여부 확인
PersistenceUnitUtil.isLoaded(Object Entity)
- 프록시 클래스 확인 방법
entity.getClass().getName()
- 프록시 강제 초기화
org.hibernate.Hibernate.initialize(entity);
- 참고 : JPA 표준은 강제 초기화가 없다.
강제 호출 : member.getName();