프록시

도윤·2024년 3월 6일

프록시

나는 둘 중에 하나만 조회하고 싶어

Member 와 Team 이 연관관계로 매핑되어 있다 하자. 그럼 Member 만 조회해야 할 때도 있고, Team 만 조회 해야 할 때도 있고, Member 와 Team 을 모두 조회해야 할 때도 있따.

Member 와 Team 함께 출력

private static void printMemberAndTeam(Member member) {

String username = member.getUsername();
System.out.println("username = " + username);

Team team = member.getTeam();
System.out.println("team = " + team.getName();

Member 만 출력

private static void printMember(Member member) {
System.out.println("member = " + memebr.getUsername());

em.getReference()

  • em.find() : 데이터베이스를 통해서 실제 엔티티 객체를 조회한다.
  • em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회. 즉 DB 에는 SQL 쿼리가 나가지 않는데 객체는 조회 하는 것이다.

get.Reference() 사용하기

  • em.find() 사용

em.find() 를 사용하거나 Member 객체에 관한 출력만 필요한데도 Team 테이블까지 조인해 SELECT 쿼리를 전송한다.

  • em.getReference()
// 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.getUsername());

위의 코드에서 em.find() 코드를 주석 처리하고 대신 getReference() 를 사용해서 실행시키면 SELECT SQL 이 전송되지 않는다. 그럼 이번에 찾은 member 의 값을 꺼내는 출력문의 주석을 풀어보자.

그러면 이제 다시 또 Member 와 Team 을 JOIN 한 SELECT_SQL 이 실행된다.
em.getReference() 코드를 실행할 때는 쿼리가 발생하지 않는다. em.getReference() 로 찾아온 객체를 사용할 때에 SQL 쿼리가 발생하는데, findMember.getId() 의 경우엔 이미 em.getReference() 에서 member.getId() 값을 넣어주었으므로 DB까지 다녀오지 않아도 알 수 있는 값이라 이떄도 쿼리는 발생하지 않는다. 다만 find.getUsername() 은 DB 에서 조회를 해야지만 알 수 있는 값이기 때문에 이때 DB 에 SQL 문이 전달된다.

그럼 저 em.getReference() 로 가져온 findMember는 도대체 뭘까? 출력해보자.

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

//출력로그
findMember = class hellojpa.Member$HibernateProxy$nizRdaZG

findMember 는 하이버네이트에서 만든 가짜 클래스이다.

프록시의 특징

프록시 객체의 초기화

Member memebr = em.getReference(Member.class, 1L);
member.getName();

  1. 클라이언트가 프록시 객체의 getNsame() 을 호출한다.
  2. 프록시 target 이 null 이면 영속성 컨텍스트에 초기화를 요청한다.
  3. 영속성 컨텍스트에서 DB 를 조회한다.
  4. DB 에 조회한 데이터로 실제 Entity 를 생성한다.
  5. 프록시의 target 에 member 엔티티 정보가 저장되고, target.getName() 을 했을 때 실제 객체에 존재하는 getName() 을 호출한다.

초기화 이후 getName() 을 호출하면 DB 조회는 이루어지지 않고 target 에 값이 존재하니까 객체에 있는 값을 호출한다.

  • 초기화 이후 getName() 을 호출하면 DB 조회는 이루어지지 않고 target 에 값이 존재하니까 객체에 있는 값을 호출한다.

    • 사실 프록시 객체에 대한 메커니즘은 JPA 표준 스펙에 존재하지 않는다. 하이버네이트 등의 라이브러리가 구현하기 나름이긴 하지만 기본적인 메커니즘은 위와 같다.

    프록시의 특징

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

  • 프록시 객체를 초기화 할 때, 프록시가 실제 엔티티로 교체되는게 아니라, 프록시 객체를 통해서 실제 엔티티에 접근이 가능한것이다.

  • 프록시 객체는 원본 엔티티를 상속받는다. 따라서 타입 체크 시 주의해야 한다.
    (== 비교 xxxxx, 대신 instance of를 사용해야 한다.)

  • em.find()로 찾은 객체와 em.find() 로 찾은 객체는 == 비교 시 true 가 반환된다.

위의 코드를 실행하면 에러가 터진다.

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
    PeristenceUnitUtil.isLoaded(Object entity)
Member reference = em.getReference(Member.calss, memeber1.getId());
System.out.println("reference = " + reference.getClass()); //proxy

System.out.println("isLoaded = " + em.getPersistenceUnitUtil().isLoaded(reference); //fasle

//============//
                   
Member reference = em.getReference(Member.class, memebr1.getId());
System.out.println("reference = " + reference.getClass()); //proxy
reference.getUsername();
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(reference); //true

참고 : 자바 ORM 표준 JPA 프로그래밍 - 기본편

profile
기록은 기억을 이긴다⭐

0개의 댓글