[JPA] 프록시와 연관관계

Jake·2022년 3월 3일
0

JPA

목록 보기
2/3

프록시와 연관관계 관리

인프런 김영한님 강의를 바탕으로 정리한 내용입니다.

What is 프록시?

  • em.getReference() 실제로 데이터를 사용하기 전까지는 데이터베이스 조회를 미루는 가짜(프록시)
  • 프록시 특징
    • 실제 클래스를 상속받아서 만들어지며, 실제 클래스와 겉 모양이 같다

    • 프록시 객체가 초기화된다고 해서, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니다.
      프록시 객체를 통해, 실제 엔티티로 접근 가능한 것일 뿐임!!
      → .class 등 타입 체크 시 주의 요망 (instance of 등으로 체크하는 것이 바람직)

    • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티가 반환됨

      프록시 상황에서 Spring의 동일성 보장

    • EntityManager.clear() 되어있는 상황에서, 동일한 id로 각 메서드를 호출한다고 가정

      1. find() 호출 후, getReference() 호출

        //em = EntityManager
        Member findMember = em.find(Member.class, id);
        Member referenceMember = em.getReference(Member.class, id);
        
        System.out.println(findMember.class);
        System.out.println(referenceMember.class);
        System.out.println(findMember == referenceMember);
        
        /*
        result:
        
        Member
        Member
        true
        */
        

        find()가 실행될 때 영속성 콘택스트에 등록이 된 상태임으로, getReference()를 실행해도 1차 캐시에서 바로 조회해 오기 때문에 위와 같은 결과가 나오게 됩니다.

      2. getReference() 호출 후, 다시 getReference() 호출

        Member referenceMember1 = em.getReference(Member.class, id);
        Member referenceMember2 = em.getReference(Member.class, id);
        
        System.out.println(referenceMember1.class);
        System.out.println(referenceMember2.class);
        System.out.println(findMember == referenceMember);
        
        /*
        result:
        
        MemberProxy
        MemberProxy
        true
        */
        

        마찬가지로, 2번째 조회 시 영속성 콘택스트에 등록된 프록시 객체를 가져오게 됩니다.

      3. getReference() 호출 후, find() 호출

        Member referenceMember = em.getReference(Member.class, id);
        Member findMember = em.find(Member.class, id);
        
        System.out.println(referenceMember.class);
        System.out.println(findMember.class);
        System.out.println(findMember == referenceMember);
        
        /*
        result:
        
        MemberProxy
        MemberProxy
        true
        */
        

        Spring에서는 동일성을 보장하기 위해, 영속성 콘택스트에 프록시 객체가 먼저 등록된 경우
        find() 호출 시에도 프록시 객체를 반환합니다!


준영속 상태에서의 프록시 초기화 문제

  • 프록시를 초기화하지 않은 상태로 detach() 등으로 준영속 상태로 만든 후 초기화하면
    LazyInitializationException : could not initialize proxy 에러가 발생한다
  • 즉, 프록시는 영속성 콘택스트의 도움을 받지 않으면 초기화가 불가능하다.

지연 로딩 (Lazy loading)

  • 위에서 다루었던 프록시를 디폴트로 켜주는 것

  • 지연 로딩으로 설정하면, find()를 호출해도 프록시 객체가 생성된다.

  • @ManyToOne, @OneToOne(ToOne류 매핑)은 기본이 즉시 로딩

  • 하지만 빈번하게 같이 사용하는 Entity들을 매핑할 때는 즉시 로딩 (Eager loading)이 더 바람직하다.

    실무에서 가급적 지연 로딩만 사용해야 하는 이유

    • 즉시로딩을 사용하면 전혀 예상하지 못한 SQL이 등장할 가능성이 있다

      find() 한 번 불렀는데, select 쿼리가 10개씩 나갈 수도 있는 것

    • JPQL에서 N+1 문제를 일으킨다

      • N+1 문제란?
        • 1의 작업을 실행했는데, N의 실행이 따라오는 것 (매우매우매우 지양해야 할 상황!)

          ex) JPQL로 member 리스트를 호출할 때, 매핑된 테이블이 즉시 로딩으로 설정된 상황

          List<Member> members = 
          	em.createQuery(”select m from Member m”, Member.class)
          	.getResultList()
        • 이 상황에서 JPQL이 작성하는 쿼리는 SELECT * FROM MEMBER (1)

          때문에 Member 테이블의 row 하나 당 Team을 조회하는 쿼리가 날아간다 (N)

          → N+1 문제 발생

profile
Java/Spring Back-End Developer

0개의 댓글