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

60jong·2022년 10월 25일
0

JPA

목록 보기
2/5

프록시

em.find / em.getReference 두가지로 엔티티를 조회할 수 있는데,
em.getReference는 프록시 객체를 반환해준다!!

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

프록시 객체는 실제 객체의 참조(target)를 보관 > 따라서 객체를 호출하면 프록시 객체가 실제 객체의 메소드를 대신 호출!!

객체를 호출하면 영컨에 빈 객체인 target을 초기화 요청!! (추후 DB조회)

프록시의 특징

처음 사용할때 한 번만 초기화!! (추후 변경 불가)
프록시 객체는 원본 엔티티를 상속한 클래스의 객체임.
실제 객체로 바뀌는 것이 아니라 계속 프록시 객체인데 target에 실제 엔티티의 레퍼런스가 초기화된 것임. --> '==' 사용 불가

(JPA는 한 트랜잭션 안에서는 영컨에 있는 같은 엔티티에 대해 가져온 객체들은 '동일'하다)

Member member1 = new Member();
member1.setName("member1");
em.persist(member1);

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

System.out.println("member1 == member2 : " + (member1 == member2)); // true

em.getReference()했을때 찾는 엔티티가 영속성 컨텍스트에 존재한다면 실제 엔티티가 반환된다. (없다면 프록시 객체 반환)

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
  • 프록시 클래스 확인 방법
  • 프록시 강제 초기화

즉시 로딩과 지연 로딩

즉시 로딩

기본 타입으로, 특정 엔티티 조회시, 연관관계가 매핑된 엔티티를 모두 조회하는 쿼리가 나간다.

예시

하지만 연관 관계가 많고 엔티티가 복잡할 경우, 연쇄 적인 쿼리 조회가 이뤄져 성능 이슈가 생길 수도 있다.
따라서 지연 로딩을 사용해 이를 방지할 수 있다.

지연 로딩

연관관계가 매핑된 Member - Team 의 어노테이션을 @ManyToOne(fetch = FetchType.LAZY)로 바꾸면 된다.

		// 팀 객체 생성
        Team team1 = new Team();
        team1.setName("team1");

		// 팀 객체 영속성컨텍스트에 등록
        em.persist(team1);

        Member member1 = new Member();
        member1.setName("member1");
        // 멤버에 팀 설정
        member1.setTeam(team1);

        em.persist(member1);

        em.flush();
        em.clear();
        
        Member findMember = em.find(Member.class, member1.getId());
        
        System.out.println(findMember.getTeam().getClass());
        System.out.println(findMember.getTeam().getName());

즉시 로딩, FetchType.EAGER의 경우

멤버를 조회할 때 팀도 같이 조회하기 위해 left outer join 쿼리가 사용됨을 확인할 수 있고, 팀 객체의 클래스는 Team 원본이다.

Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_3_0_,
        member0_.insert_member as insert_m2_3_0_,
        member0_.createdAt as createda3_3_0_,
        member0_.lastModifiedAt as lastmodi4_3_0_,
        member0_.update_member as update_m5_3_0_,
        member0_.city as city6_3_0_,
        member0_.name as name7_3_0_,
        member0_.street as street8_3_0_,
        member0_.team_id as team_id10_3_0_,
        member0_.zipcode as zipcode9_3_0_,
        team1_.team_id as team_id1_6_1_,
        team1_.name as name2_6_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.team_id=team1_.team_id 
    where
        member0_.MEMBER_ID=?
class hellojpa.domain.Team
team1
10월 25, 2022 2:42:35 오후 org.hibernate.e

지연 로딩, FetchType.LAZY의 경우에는

멤버 엔티티를 조회할 떄 연관관계가 설정돼있는 Team은 조회하지 않고 조회하는 쿼리가 나갔다. 그리고 그 상태로의 팀 객체는 프록시 객체임을 확인할 수 있다.

이 프록시 객체는 실제로 팀 객체를 사용하는 경우에 팀에 대한 쿼리를 내보내서 초기화함을 확인할 수 있다.

Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_3_0_,
        member0_.insert_member as insert_m2_3_0_,
        member0_.createdAt as createda3_3_0_,
        member0_.lastModifiedAt as lastmodi4_3_0_,
        member0_.update_member as update_m5_3_0_,
        member0_.city as city6_3_0_,
        member0_.name as name7_3_0_,
        member0_.street as street8_3_0_,
        member0_.team_id as team_id10_3_0_,
        member0_.zipcode as zipcode9_3_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?
class hellojpa.domain.Team$HibernateProxy$pqGWGMgS
Hibernate: 
    select
        team0_.team_id as team_id1_6_0_,
        team0_.name as name2_6_0_ 
    from
        Team team0_ 
    where
        team0_.team_id=?
team1

지연 로딩을 사용할지 말지는, 비즈니스 로직에 달려있다.
멤버와 팀을 거의 같이 조회해 사용하는 경우에는 즉시 로딩을 사용하는 것이 바람직할 것이고, 그렇지 않을 경우에는 지연 로딩을 사용하는 것이 성능에 더 좋을 것이다.

하지만,

실무에서는 아래의 이유들로 가급적 지연 로딩만을 사용할 것이 권장된다.

  • 즉시 로딩에 경우 테이블을 조인해서 조회하는데, 연관 관계가 많아질 경우에 엄청나게 많은 조인이 수반되고 성능이 크게 저하될 수 있다.

  • 즉시 로딩은 JPQL에서 N+1 문제를 야기한다. JPQL은 SQL로 변역되어 DB에 보내진다. N명의 멤버를 모두 조회하는 경우에, select m from Member m을 보내면 N명의 멤버의 연관관계가 있는 팀을 조회하기 위해서는 N개의 팀 조회 쿼리가 보내져야 한다.

    // 2명의 멤버를 조회한 경우 (즉시 로딩)
    Hibernate: 
        /* select
            m 
        from
            Member m */ select
                member0_.MEMBER_ID as member_i1_3_,
                member0_.insert_member as insert_m2_3_,
                member0_.createdAt as createda3_3_,
                member0_.lastModifiedAt as lastmodi4_3_,
                member0_.update_member as update_m5_3_,
                member0_.city as city6_3_,
                member0_.name as name7_3_,
                member0_.street as street8_3_,
                member0_.team_id as team_id10_3_,
                member0_.zipcode as zipcode9_3_ 
            from
                Member member0_
    Hibernate: 
        select
            team0_.team_id as team_id1_6_0_,
            team0_.name as name2_6_0_ 
        from
            Team team0_ 
        where
            team0_.team_id=?
    Hibernate: 
        select
            team0_.team_id as team_id1_6_0_,
            team0_.name as name2_6_0_ 
        from
            Team team0_ 
        where
            team0_.team_id=?

    따라서 @XToOne 어노테이션은 항상 지연 로딩으로 세팅을 바꿔주자!! (@XToMany는 지연 로딩이 기본 값)

profile
울릉도에 별장 짓고 싶다

0개의 댓글