Posts [자바 ORM 표준 JPA 프로그래밍-기본편] 프록시와 연관관계 관리
Post
Cancel

[자바 ORM 표준 JPA 프로그래밍-기본편] 프록시와 연관관계 관리

본 포스팅은 인프러의 JPA 기본편을 수강하고 정리하는 내용입니다.


프록시

Member를 조회할 때 Team도 함께 조회해야 할까?

image

회원과 팀 함께 출력

1
2
3
4
5
6
public void printUserAndTeam(String memberId) {
    Member member = em.find(Member.class, memberId);
    Team team = member.getTeam();
    System.out.println("회원 이름: " + member.getUsername());
    System.out.println("소속팀: " + team.getName());
}
  • 회원만 출력
1
2
3
4
5
public void printUser(String memberId) {
    Member member = em.find(Member.class, memberId);
    Team team = member.getTeam();
    System.out.println("회원 이름: " + member.getUsername());
}

어느 경우엔 Member객체를 가져올때 Team 객체를 같이 가져와야되고 어떨때는 Team은 가져올 필요가 없고 최적화가 필요하다.

프록시 기초

  • em.find() vs em.getReference()
  • em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
    • DB에 쿼리가 안나가는데 가짜 객체가 조회된다.

image

1
2
3
4
Member findMember = em.getReference(Member.class, member.getId()); // DB 쿼리 질의 안함
System.out.println("findMember = " + findMember.getClass()); // Hibernate가 강제로 만들어낸 proxy 클래스 (가짜 객체)
System.out.println("findMember.id = " + findMember.getId()); // id는 paramter로 너은거라 DB에서 안가져와도 알고 있음
System.out.println("findMember.name = " + findMember.getUsername()); // 실제 객체의 멤버변수에 접근할때 DB에 쿼리 질의

Hibernate가 내부의 라이브러리를 써서 proxy라는 가짜 entity를 준다. proxy는 껍데기는 똑같은데 안에가 텅텅 비어있다. 내부에는 target이란게 있는데 실제 entity를 가리킨다.

프록시 특징

  • 실제 클래스를 상속 받아서 만들어짐
  • 실제 클래스와 겉 모양이 같다.
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
    • 상속관계는 부모타입으로 보면서 사용하면 되기 때문에

image

프록시 특징

  • 프록시 객체는 실제 객체의 참조(target)를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
    • 예를 들어, getName()을 호출하면 실제 target의 getName()을 대신 호출해준다.

image

프록시 객체의 초기화

1
2
Member member = em.getReference(Member.class, “id1”);
member.getName();

image

  • 1)클라이언트가 프록시 객체의 getName() 호출
  • 2)프록시 객체가 target이 null이면 영속성 컨텍스트에 진짜 객체를 요청한다.(초기화 요청)
  • 3)영속성 컨텍스트가 DB를 조회한다.
  • 4)실제 entity가 생성된다.
  • 5)프록시 target이 생성된 member 엔티티를 가리키고 있어 실제 member 엔티티의 getName() 이 호출된다.

Note: 사실 프록시 객체의 매커니즘은 JPA 표준 스펙엔 없다. 다른 라이브러리든 구현하기 나름인데 위와 같은 메커니즘으로 동작한다는걸 이해하면 된다.

프록시의 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
  • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)
    • == 비교를 하면 프록시 객체와 실제 객체와 타입이 안맞기에 실패한다. ~~~ Member member1 = new Member(); member1.setUsername(“member1”); em.persist(member1);

    Member member2 = new Member(); member2.setUsername(“member2”); em.persist(member2);

    em.flush(); em.clear();

    Member m1 = em.find(Member.class, member1.getId()); Member m2 = em.getReference(Member.class, member2.getId());

    System.out.println(“m1 == m2: “ + (m1.getClass() == m2.getClass())); System.out.println(“m1 == m2: “ + (m1 instanceof Member)); System.out.println(“m1 == m2: “ + (m2 instanceof Member)); ~~~

  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
    • image
    • image
    • 프록시로 한 번 조회가 되면 em.find() 도 == 비교를 맞추기 위해 프록시를 반환해버린다.
    • 개발 할 때 프록시든 아니든 문제가 없게 개발하는게 중요한 것 이다.
    • JPA는 == 비교를 무조건 맞춘다는 것을 기억하자.
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)
    • image
    • 실무에 많이 만나게 되는 문제이다.
    • 웹 애플리케이션을 개발한다하면 보통 트랜잭션과 영속성 컨텍스트의 시작과 끝을 맞추는데 트랜잭션 끝나고 나서 프록시를 조회하려 하면 No Session~ 하고 에러가 발생한다.

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
    • PersistenceUnitUtil.isLoaded(Object entity)
    • image
  • 프록시 클래스 확인 방법
    • entity.getClass().getName() 출력(..javasist.. or HibernateProxy…)
      1
      2
      
      Member refMember = em.getReference(Member.class, member1.getId()); // 영속성 컨텍스트에 올라가 있는 상태
      System.out.println("refMember = " + refMember.getClass());
      
  • 프록시 강제 초기화
    • org.hibernate.Hibernate.initialize(entity); ~~~ Member refMember = em.getReference(Member.class, member1.getId()); // 영속성 컨텍스트에 올라가 있는 상태 System.out.println(“refMember = “ + refMember.getClass()); System.out.println(“before isLoaded = “ + emf.getPersistenceUnitUtil().isLoaded(refMember));

    Hibernate.initialize(refMember); // 강제 초기화 System.out.println(“after isLoaded = “ + emf.getPersistenceUnitUtil().isLoaded(refMember)); ~~~

  • 참고: JPA 표준은 강제 초기화 없음
    • 강제 호출: member.getName()
This post is licensed under CC BY 4.0 by the author.

[자바 ORM 표준 JPA 프로그래밍-기본편] 실전 예제4 - 상속관계 매핑

[자바 ORM 표준 JPA 프로그래밍-기본편] 즉시 로딩과 지연 로딩