코딩과 결혼합니다

[JPA] 프록시 본문

2세/JPA

[JPA] 프록시

코딩러버 2024. 2. 21. 15:21
728x90

프록시, 왜 사용할까?

프록시는 AOP(관점지향프로그래밍)의 핵심 개념으로, 특정 로직을 캡슐화하여 메서드 호출 전후에 실행되도록 하는 방식이다. 프록시는 대상 객체를 감싸서 대상 객체의 메서드를 호출하기 전후에 특정 작업을 수행할 수 있게 해 준다. 이러한 방식으로 코드 중복을 줄이고 관심사의 분리를 달성할 수 있다.

 

🤔 Member를 조회할 때 Team도 함께 조회해야 하나?

  • 비즈니스 1 - member의 이름과 team의 이름을 함께 조회
    • Member class 안에는 Team 객체가 있다. 
    • member에서 쿼리 1번, team에서 쿼리 1번 한 번에 쿼리를 한번에 날리는 방법은 없나?
  • 비즈니스 2 - member의 이름만 조회
    • 나는 Team 정보는 필요 없는데 낭비네...

⭐JPA의 지연 로딩과 프록시를 가지고 해결!


em.find()  VS  em.getReference()

em.find() : 데이터베이스를 통해서 실제 엔티티 객체를 조회한다.

em.getReference()  : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회 

Member findMember = em.getReference(Member.class, member.getId());

System.out.prinln("findMember.id = " + findMember.getId()); //쿼리가 나가지 않음
System.out.prinln("findMember.username = " + findMember.getUsername()); //select 쿼리

 

findMember로 레퍼런스를 찾을 때 이미 아이디 값을 넣었기 때문에 id를 호출할 때에는 db에 쿼리 할 필요가 없지만,

getUsername을 호출할 때에는 이 데이터가 DB에 있기 때문에 쿼리를 날리게 된다.

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

가짜 엔티티 조회를 할 때에는 어떤 클래스를 사용할까? Member$HibernateProxy$(생략) 라는 클래스를 사용함을 확인할 수 있었다.

 

프록시 객체의 초기화

  • 실제 클래스를 상속 받아서 만들어진다. ➡️ 실제 클래스와 겉모양이 같다.
  • 안에는 텅 비어있고 내부에 Target이라는 게 있는데 이게 진짜 레퍼런스를 가리킨다.
  • 이론상으로 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.

프록시 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화된다. (그리고 그것을 계속 사용한다.)
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. (실제 엔티티에 접근할 수 있게 됨)
Member findMember = em.getReference(Member.class, member.getId());

System.out.prinln("findMember.username = " + findMember.getUsername()); //프록시 객체, 실제 엔티티X
System.out.prinln("after findMember = " + findMember.getClass()); //Member$HibernateProxy$(생략)
  • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크 시 주의해야 한다.( == 비교 X / instance of 사용 O)
    • 타입이 프록시로 넘어올지 실제로 넘어올지 구분이 안되기 때문에 == 을 사용 X
  • 영속성 컨텍스트에서 찾는 엔티티가 있으면 em.getReference()를 호출해도 실제 엔티티 반환 (반대도 성립)
    • JPA 에서는 같은 영속성 컨텍스트, 같은 트랜젝션 레벨 안에서 '==' 조회를 하면 항상 같다고 나온다. 
Member m1 = em.find(Member.class, member1.getId());
System.out.prinln("m1 = " + m1.getClass()); //Member Class

Member reference = em.getReference(Member.class, member1.getId());
System.out.prinln("reference = " + reference.getClass()); //Member Class
  • 준영속 상태일 때, 프록시를 초기화하면 문제 발생(org.hibernate.LazyInitializationException)
    • 프록시에 대한 초기화 요청은 영속성 컨텍스트를 통해서 일어난다.
Member reference = em.getReference(Member.class, member1.getId());
System.out.prinln("reference = " + reference.getClass()); //Proxy

em.detach(reference); //영속성 컨텍스트에서 관리 안함
em.close(); //영속성 컨텍스트 종료

reference.getUsername(); //org.hibernate.LazyInitializationException