프록시란?
실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체를 의미
- em.find()
- 데이터베이스를 통해서 실제 Entity 객체 조회 (쿼리 날라감)
- em.getReference()
- 데이터베이스 조회를 미루는 가짜(프록시) Entitty 조회
- (DB에 쿼리가 안 나가는데 객체가 조회가 됨.)
//Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId());
//System.out.println("findMember.id = " + findMember.getId());
//System.out.println("findMember.username = " + findMember.getName());
아래 출력문들을 주석처리하고, getReference만 호출했을 때는 select 쿼리가 나오지 않고 끝나는 것을 확인할 수 있음.
//Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getName());
아래 출력문들을 주석을 해제한다면??
- System.out.println("findMember.id = " + findMember.getId()); 는 DB를 건들지 않기에 쿼리가 나가지 않음
- ⇒ em.getReference(Member.class, member.getId());를 할 때 이미 ID 값을 넣었기에 이미 값이 있음 (파라미터로 넣음)
- ⭐System.out.println("findMember.username = " + findMember.getName());에서 Name은 DB에 있기에 쿼리가 나감⭐
- .getName()을 호출하는 시점에 DB에 쿼리를 날려 Name을 찾고 findMember의 값을 채워 출력
System.out.println("findMember = " + findMember.getClass());
- findMember.getClass() 를 출력하면 이름이 Member$HibernateProxy$Hz1uC9Un 로 출력
- 하이버네이트가 강제로 만들어낸 가짜 클래스라는 의미 (프록시 클래스)
- 하이버네이트가 자기 내부의 라이브러리를 사용해 ‘프록시’라고 하는 가짜 엔티티를 줌.
- 위 그림을 보면) 외부는 똑같지만, 내부는 텅텅 비어있는 것.
프록시 특징
- 실제 클래스를 상속 받아서 만들어짐 (하이버네이트가 내부적으로 라이브러리를 사용해 만들어냄)
- 실제 클래스와 겉 모양은 같음
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨 (이론상)
- 프록시 객체는 실제 객체의 참조(target)를 보관
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출
- if getName()을 호출한다면 → 실제 타겟에 있는 getName()을 대신 호출해줌.
- 처음에는 타겟이 없음. (실제 DB에서 조회한 적이 없기 때문)
프록시 객체의 초기화
Member member = em.getReference(Member.class, "id1");
member.getName();
getReference 이므로 프록시 객체를 가져오게 됨 (실제 객체 X)
- member.getName();을 호출할 때 초반에는 DB에 객체가 없으므로
- JPA가 ⭐영속성 컨텍스트에 요청⭐을 함
- 영속성 컨텍스트를 통해 초기화 요청
- 초기화 : DB를 통해 실제 값을 가져와 엔티티를 만들어내는 과정
- 영속성 컨텍스트는 DB를 조회해
- 실제 Entity를 생성해 넣어줌
- 그 이후 프록시 target에 실제 객체를 연결시켜줌.
⇒ 그 이후 이제는 getName() 호출하면, 실제 getName()을 통해 반환이 됨
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.username = " + findMember.getName());
System.out.println("findMember.username = " + findMember.getName());
findMember.getName()을 2번 호출하면
1번째 : DB에 객체가 없으므로 쿼리 날려 가져옴
2번째 : 타겟이 이미 값이 있으므로 바로 출력
프록시의 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 바로 위 예시 코드 참고
- ⭐프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님!⭐ (교체되는 게 아님. 프록시는 유지가 되고 내부의 값만 채워짐)
- 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능한 것임!
- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)⭐
- 프록시인 객체와 프록시가 아닌 객체는 타입이 맞지 않음⭐
Member member1 = new Member();
member1.setName("member1");
em.persist(member1);
Member member2 = new Member();
member2.setName("member2");
em.persist(member2);
em.flush();
em.clear();
Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId()); //getReference : 가짜 엔티티 조회
System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass())); // false 출력 : 프록시인 객체와 프록시가 아닌 객체는 타입이 맞지 않음
System.out.println("m1 instanceof m2 : " + (m1 instanceof Member)); // true 출력
System.out.println("m1 instanceof m2 : " + (m2 instanceof Member)); // true 출력
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환⭐
Member reality = em.find(Member.class, member1.getId());
System.out.println("reality = " + reality.getClass());
Member reference = em.getReference(Member.class, member1.getId());
//위에서 이미 em.find를 했기에 엔티티가 이미 있으므로 em.getReference()를 호출해도 실제 엔티티가 반환됨.
System.out.println("reference = " + reference.getClass());
System.out.println("reality == reference : " + (reality == reference));
- IF 반대로 em.getReference를 먼저 하고, em.find를 이후에 한다면?⇒ 그래야 reality == reference 를 true로 보장하기 때문.
- 이후 em.find한 결과도 프록시 객체로 반환이 됨.
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
- 하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());
em.detach(reference); //reference를 영속성 컨텍스트에서 관리 안 해!
reference.getName();
System.out.println("reference = " + reference.getName());
em.detach(reference); 로 reference를 영속성 컨텍스트에서 관리하지 않는다고
선언하면 아래와 같이 proxy를 초기화할 수 없다고 뜸
(영속성 컨텍스트를 통해 DB를 통해 실제 값을 가져와 엔티티를 만들어내므로
이를 닫거나 detach 해버리면 아래와 같이 뜸)
could not initialize proxy [hellojpa.Member#1] - no Session
⇒ 엄청 많이 만나는 예외 !! 기억하깅
반응형
'Study > JPA' 카테고리의 다른 글
프로젝션과 페이징 (0) | 2024.07.27 |
---|---|
객체지향 쿼리 언어 소개 (0) | 2024.07.27 |
값 타입 2 (0) | 2024.07.27 |
값 타입 1 (0) | 2024.07.27 |
프록시 ) 영속성 전이(CASCADE)와 고아 객체 (0) | 2024.07.26 |