[JPA] 프록시

Noah-wilson·2025년 1월 9일

JPA

목록 보기
9/10

프록시(Proxy)란?

프록시(Proxy)란, 영문 그대로 '대리자'라는 뜻으로, 실제 객체를 대신하여 대리 역할을 수행하는 객체를 말한다.

위 사진과 같이 프록시는 실제 클래스를 상속받아서 만들어지기떄문에 실제 클래스 똑같이 생겼다.
이론상 사용자 관점에서는 진짜 객체인지 프록시인지 구분하지 않고 사용할 수 있다.

프록시의 동작 과정

프록시 객체는 실제 객체의 참조(target)을 가지고 있으므로 프록시 객체를 호출하면 실제 객체의 메소드를 호출한다.

em.getReference()를 하여 프록시 객체를 생성할 수 있다.

다음 그림은 em.getReference()을 통해 MemberProxy를 생성후 .getName()을 호출했을떄 동작 과정을 나타낸 그림이다.(영속성 컨텍스트가 클린(clean)한 상태 가정)

  1. getName() 호출
  2. 프록시(MemberProxy)는 영속성 컨텍스트에 초기화 요청을 한다.
  3. member를 영속화하기 위해 DB 조회
  4. 실제 member 객체 영속화 후 프록시(MemberProxy)의 참조(target)과 연결한다.
  5. 프록시(MemberProxy)가 참조(target)의 메소드(getName)을 호출후 반환한다.
+ 참고
em.find()는 데이터 베이스를 통해서 실제 엔티티 객체 조회한다.
em.getReference()는 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회한다.

정리

  • 프록시 객체는 처음 사용할 때 한 번만 초기화된다.

  • 프록시를 초기화한다는 것은 프록시 객체가 실제 엔티티로 바뀌는것이 아니다.

            Member findMember = em.getReference(Member.class, member.getId());
            System.out.println("findMember.getClass() = " + findMember.getClass());

            findMember.getUsername();

            System.out.println("findMember.getClass() = " + findMember.getClass());

실행 결과:

findMember.getClass() = class hellojpa.Member$HibernateProxy$NXbKWHsJ
Hibernate: 
    select
        m1_0.MEMBER_ID,
        m1_0.createBy,
        m1_0.createdDate,
        m1_0.lastModifiedBy,
        m1_0.lastModifiedDate,
        t1_0.TEAM_ID,
        t1_0.name,
        m1_0.USERNAME 
    from
        Member m1_0 
    left join
        Team t1_0 
            on t1_0.TEAM_ID=m1_0.team_TEAM_ID 
    where
        m1_0.MEMBER_ID=?
findMember.getClass() = class hellojpa.Member$HibernateProxy$NXbKWHsJ
  • 프록시 객체는 원본 엔티티를 상속 받으므로
    타입체크 를 주의해야 한다. 타입 체크시 == 비교를 사용하지 않고 instance of를 사용하자.

    
                Member RefMember = em.getReference(Member.class, member.getId());
               System.out.println("RefMember.getClass() = " + RefMember.getClass());
               System.out.println(" RefMember instanceof Member = " + (RefMember instanceof Member));

    실행결과:

    RefMember.getClass() = class hellojpa.Member$HibernateProxy$uyIJHorZ
    RefMember instanceof Member = true
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해 도 실제 엔티티 반환된다.

            Member findMember = em.find(Member.class, member.getId());
            System.out.println("findMember.getClass() = " + findMember.getClass());

            findMember.getUsername();
            Member RefMember = em.getReference(Member.class, member.getId());
            System.out.println("RefMember.getClass() = " + RefMember.getClass());

실행 결과:

findMember.getClass() = class hellojpa.Member
findMember.getClass() = class hellojpa.Member
  • ==비교가 필요할 경우를 대비하여 jpa는 항상 한 트렌잭션 안 에서 같은 엔티티에 대한 동일성을 보장을 해준다.(==는 항상 true여야 한다.)
            System.out.println("================");
            Member findMember = em.find(Member.class, member.getId());
            System.out.println("findMember.getClass() = " + findMember.getClass());

            Member RefMember = em.getReference(Member.class, member.getId());
            System.out.println("RefMember.getClass() = " + RefMember.getClass());

            em.clear();
            System.out.println("================");
            Member RefMember2 = em.getReference(Member.class, member.getId());
            System.out.println("RefMember2.getClass() = " + RefMember2.getClass());

            Member findMember2 = em.find(Member.class, member.getId());
            System.out.println("findMember2.getClass() = " + findMember2.getClass());
            
            System.out.println("findMember == RefMember:" + (findMember==RefMember));
            System.out.println("findMember2 == RefMember2:" + (findMember2==RefMember2));

실행 결과:

================
Hibernate: 
    select
        m1_0.MEMBER_ID,
        m1_0.createBy,
        m1_0.createdDate,
        m1_0.lastModifiedBy,
        m1_0.lastModifiedDate,
        t1_0.TEAM_ID,
        t1_0.name,
        m1_0.USERNAME 
    from
        Member m1_0 
    left join
        Team t1_0 
            on t1_0.TEAM_ID=m1_0.team_TEAM_ID 
    where
        m1_0.MEMBER_ID=?
findMember.getClass() = class hellojpa.Member
RefMember.getClass() = class hellojpa.Member
================
RefMember2.getClass() = class hellojpa.Member$HibernateProxy$dfy1PGsC
Hibernate: 
    select
        m1_0.MEMBER_ID,
        m1_0.createBy,
        m1_0.createdDate,
        m1_0.lastModifiedBy,
        m1_0.lastModifiedDate,
        t1_0.TEAM_ID,
        t1_0.name,
        m1_0.USERNAME 
    from
        Member m1_0 
    left join
        Team t1_0 
            on t1_0.TEAM_ID=m1_0.team_TEAM_ID 
    where
        m1_0.MEMBER_ID=?
findMember2.getClass() = class hellojpa.Member$HibernateProxy$dfy1PGsC
================
findMember == RefMember:true
findMember2 == RefMember2:true
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면문제 발생
    (하이버네이트는org.hibernate.LazyInitializationException 예외를 터트림)
            Member refMember = em.getReference(Member.class, member.getId());
            
            em.detach(refMember);

            System.out.println("refMember.getUsername() = " + refMember.getUsername());
org.hibernate.LazyInitializationException: could not initialize proxy [hellojpa.Member#1] 

//이하 생략...

참고 자료 출처: 출처

0개의 댓글