em.find(); // 데이터베이스를 통해 실제 엔티티 객체를 조회
em.getReference(); // 데이터베이스 조회를 하지 않은 가짜(프록시) 엔티티 객체를 조회
getReference() 메서드로 호출된 프록시 엔티티 객체는 껍데기는 똑같지만 내용물은 비어있다. 다만 target에서 실제 클래스를 참조한다.
프록시 특징
- target 클래스를 상속 받아서 만들어진다.(Hibernate가 내부적으로 상속시켜줌)
- 실제 클래스와 겉 모양은 같다.
- 사용하는 입장에서는 프록시 객체인지, 실제 객체인지 구분하지 않고 사용하면 된다.
- 프록시 객체가 실제 객체의 참조(target)를 보관한다.
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
Member member = em.getReference(Member.class, "id1"); // 프록시 객체 생성
member. getName(); // 프록시 객체의 초기화 요청
위와같이 member가 Member의 프록시 객체일때 getName()으로 name값을 얻으려 하면 영속성 컨텍스트에 name값을 요청하게 된다. 영속성 컨텍스트에는 값이 없으므로 DB에 조회를 요청하고, DB에서 실제 엔티티를 생성한다. 그렇게 생성된 엔티티를 비어있던 프록시의 target과 연결을 함으로써 target.getName을 통해 값을 얻을 수 있다.
이렇게 한번 프록시 객체를 통해 Member객체를 조회했다면 영속성 컨텍스트에 저장되기때문에 다음 조회시엔 DB에 접근하지 않는다.
- 따라서 프록시 객체는 처음 사용할때 한번만 초기화가 가능하다.
- 프록시 객체는 원본 엔티티를 상속받기 때문에 타입을 체크할때 == 비교가 아닌 instance of 로 비교해야한다.
- 원본 엔티티가 1차캐시에 있을땐 getReference로 프록시 객체를 불러오려해도 1차캐시에 이미 원본 엔티티가 있으므로 프록시 객체가아닌 원본 객체가 불려온다.
Member findMember = em.find(Member.class, member.getId());
Member refMember = em.getReference(Member.class, member.getId());
System.out.println("findMember = " + findMember.getClass()); // Member
System.out.println("refMember = " +refMember.getClass()); // Proxy가 아닌 Member
System.out.println("findMember == refMember : " + (findMember == refMember)); // true
find를 통해 Member객체가 영속상태이므로 같은 Member를 em.getReference를 통해 참조하더라도 1차 캐시에 있는 Member를 가져오게 된다. 반대의 경우도 생각해보자면 getReference를 통해 Proxy객체를 1차캐시에 저장하게 되면 그 후 em.find()로 Member객체를 가져와도 Member 객체를 가져오는게 아닌 1차캐시에 있는 Proxy객체를 가져오게 된다. 이는 JPA의 특성중 하나로 JPA는 같은 트랜잭션 내에있는 같은 객체의 비교는 항상 true를 반환하게끔 설계되어 있기 때문이다.
'스프링' 카테고리의 다른 글
[스프링] DI, IoC, Bean (0) | 2023.02.13 |
---|---|
[스프링] 즉시로딩과 지연로딩 (0) | 2023.02.10 |
[스프링] 기본키 관련 매핑 Annotation (0) | 2023.02.09 |
[스프링] 필드와 컬럼 매핑 Annotation (0) | 2023.02.09 |
[스프링] 영속성 컨텍스트란 (0) | 2023.02.09 |