JPA 프록시와 연관관계 - 즉시 로딩과 지연 로딩

2022. 7. 4. 21:41·공부/JPA
반응형

출처 : 자바 ORM 표준 JPA 프로그래밍 인프런 강의

 

JPA 게시글은 대부분 인프런의 김영한님의 강의인 '자바 ORM 표준 JPA 프로그래밍' 기반으로 내용을 정리했습니다.

 

즉시 로딩과 지연 로딩

나는 여전히 Member만을 조회하고 싶어.

단순히 Member 정보만 사용하는 비즈니스 로직 같은 경우에 어떻게 Member만 조회해올 수 있을까? 이전 글에서는 프록시에 대해 배웠지만 사실 그건 잘 사용하지 않고, 지연 로딩을 사용해서 프록시로 조회하는 방법을 많이 사용한다.

 

지연 로딩 LAZY

// Member.class 중 일부

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;

Member 클래스에서 Team과 연관관계 매핑 애노테이션에 fetch = FetchType.LAZY 속성을 넣어주면 된다. LAZY 속성을 설정하면 해당 Team 객체를 프록시 객체로 조회하고 Member 클래스만 DB에서 조회하게 된다.

// 실행 클래스에서 Member 조회
Member m = em.find(Member.class, member1.getId());


// 출력 로그
Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_3_0_,
        member0_.createdBy as createdB2_3_0_,
        member0_.createdDate as createdD3_3_0_,
        member0_.lastModifiedBy as lastModi4_3_0_,
        member0_.lastModifiedDate as lastModi5_3_0_,
        member0_.TEAM_ID as TEAM_ID7_3_0_,
        member0_.USERNAME as USERNAME6_3_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?

실행 클래스에서 Member를 조회하게 되면 SELECT 쿼리에 member만 조회된 걸 확인할 수 있다.

 

//...생략

Member m = em.find(Member.class, member1.getId());
System.out.println("m = " + m.getTeam().getClass());
System.out.println("==========");
m.getTeam().getName();
System.out.println("==========");

//...생략

자 이제 추가로 member 객체에 team 데이터를 넣고 위의 코드를 실행하면 Team을 getClass()하는 경우 프록시 객체 정보가 반환된다. 그리고 Team과 관련된 데이터를 호출할 때는 그제서야 Team에 프록시 초기화가 되면서 DB에 SELECT 쿼리가 전송된다.

// 출력 로그

m = class hellojpa.Team$HibernateProxy$6DzDe8wW
==========
Hibernate: 
    select
        team0_.TEAM_ID as TEAM_ID1_5_0_,
        team0_.createdBy as createdB2_5_0_,
        team0_.createdDate as createdD3_5_0_,
        team0_.lastModifiedBy as lastModi4_5_0_,
        team0_.lastModifiedDate as lastModi5_5_0_,
        team0_.name as name6_5_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
==========

 

 

Member와 Team을 자주 함께 사용한다면?

LAZY말고 EAGER를 사용하면 된다. Member 클래스에서 연관관계 매핑 fetch 속성을 수정한다.

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;

그러면 처음 Member를 SELECT 하는 쿼리가 전송될 때의 SELECT에서 Member와 Team을 JOIN해서 한 번에 가져온다.

// 출력 로그
Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_3_0_,
        member0_.createdBy as createdB2_3_0_,
        member0_.createdDate as createdD3_3_0_,
        member0_.lastModifiedBy as lastModi4_3_0_,
        member0_.lastModifiedDate as lastModi5_3_0_,
        member0_.TEAM_ID as TEAM_ID7_3_0_,
        member0_.USERNAME as USERNAME6_3_0_,
        team1_.TEAM_ID as TEAM_ID1_5_1_,
        team1_.createdBy as createdB2_5_1_,
        team1_.createdDate as createdD3_5_1_,
        team1_.lastModifiedBy as lastModi4_5_1_,
        team1_.lastModifiedDate as lastModi5_5_1_,
        team1_.name as name6_5_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.TEAM_ID=team1_.TEAM_ID 
    where
        member0_.MEMBER_ID=?

이렇게 em.find()로 가져올 때부터 연관관계에 있는 객체에 대한 테이블을 JOIN 해오는게 즉시 로딩이다.

 

 

프록시와 즉시로딩 주의

  • 가급적 지연 로딩만 사용한다.(특히 실무에서)
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다.
  • 즉시 로딩은 JPQL에서 N + 1 문제를 일으킨다.
// ... 생략

Team team = new Team();
team.setName("teamA");
em.persist(team);

Team teamB = new Team(); 
teamB.setName("teamB");
em.persist(teamB);

Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);

Member member2 = new Member();
member2.setUsername("member2");
member2.setTeam(teamB);
em.persist(member2);

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

List<Member> members = em.createQuery("select m from Member m", Member.class)
     .getResultList();
     
// ... 생략

위의 코드에서는 member1과, member2가 존재하며 각각 다른 팀을 가지고있다. 이 둘을 JPQL SELECT문으로 member를 조회해오면 member1의 팀과 member2의 팀에 대한 데이터를 가져오기 위해 SELECT가 두 번 나가게 된다. 

// 출력 로그

Hibernate: 
    /* select
        m 
    from
        Member m */ select
            member0_.MEMBER_ID as MEMBER_I1_3_,
            member0_.createdBy as createdB2_3_,
            member0_.createdDate as createdD3_3_,
            member0_.lastModifiedBy as lastModi4_3_,
            member0_.lastModifiedDate as lastModi5_3_,
            member0_.TEAM_ID as TEAM_ID7_3_,
            member0_.USERNAME as USERNAME6_3_ 
        from
            Member member0_
Hibernate: 
    select
        team0_.TEAM_ID as TEAM_ID1_5_0_,
        team0_.createdBy as createdB2_5_0_,
        team0_.createdDate as createdD3_5_0_,
        team0_.lastModifiedBy as lastModi4_5_0_,
        team0_.lastModifiedDate as lastModi5_5_0_,
        team0_.name as name6_5_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
Hibernate: 
    select
        team0_.TEAM_ID as TEAM_ID1_5_0_,
        team0_.createdBy as createdB2_5_0_,
        team0_.createdDate as createdD3_5_0_,
        team0_.lastModifiedBy as lastModi4_5_0_,
        team0_.lastModifiedDate as lastModi5_5_0_,
        team0_.name as name6_5_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?

N + 1은 결국에 1 = JPQL로 제일 처음 전달된 쿼리, N = 처음 쿼리로 인해 추가 쿼리가 N개가 나간다는 의미이다. 이 문제는 LAZY로 설정하면 해결되는 문제이다.

  • @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 LAZY로 설정해주어야 한다.
  • @OneToMany, @ManyToMany는 기본이 지연 로딩이다.
  • 한 번에 조회해야 할 때는 fetch join을 사용한다. (후술)

 

 

지연 로딩 활용 - 이론

  • Member와 Team은 자주 함께 사용 -> 즉시 로딩
  • Member와 Order는 가끔 사용 -> 지연 로딩
  • Order와 Product는 자주 함께 사용 -> 즉시로딩

 

 

지연 로딩 활용 - 실무

  • 모든 연관관계에 지연 로딩을 사용해라!
  • 실무에서 즉시 로딩을 사용하지 마라!
  • JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라! (후술)
  • 즉시 로딩은 상상하지 못한 쿼리가 나가 성능 이슈를 불러올 수 있다.
반응형
저작자표시 비영리 변경금지 (새창열림)
'공부/JPA' 카테고리의 다른 글
  • JPA 값 타입 - 기본값 타입
  • JPA 프록시와 연관관계 - 영속성 전이(CASCADE)와 고아 객체
  • JPA 프록시와 연관관계 - 프록시
  • JPA 고급 매핑 - Mapped Superclass 매핑 정보 상속
데부한
데부한
어차피 할 거면 긍정적으로 하고 싶은 개발자
    반응형
  • 데부한
    동동이개발바닥
    데부한
  • 전체
    오늘
    어제
    • 분류 전체보기 (307)
      • 방통대 컴퓨터과학과 (27)
        • 잡담 (9)
        • 3학년1학기 (17)
      • 프로젝트 및 컨퍼런스 회고 (1)
        • 프로젝트 (4)
        • 한이음 프로젝트 (0)
        • 회고 (3)
      • 공부 (165)
        • Spring (37)
        • JPA (71)
        • 인프런 워밍업 클럽_BE (10)
        • Java (6)
        • React.js (27)
        • 넥사크로 (11)
        • 기타 (3)
      • 알고리즘 (85)
        • 알고리즘 유형 (10)
        • 알고리즘 풀이 (57)
        • SQL 풀이 (18)
      • 에러 해결 (13)
      • 잡담 (7)
        • 국비교육 (2)
        • 구매후기 (5)
        • 진짜 잡담 (0)
  • 블로그 메뉴

    • Github
    • Linkedin
    • 홈
    • 방명록
    • 글쓰기
    • 관리
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    백준
    IT
    Spring
    Java
    스프링부트
    인프런
    운영체제
    전자정부프레임워크
    기출문제
    코딩테스트
    알고리즘
    프로그래머스
    방통대
    JPA
    SQL
    개발자
    프론트엔드
    자바스크립트
    react
    토비의스프링부트
    oracle
    RESTful
    SpringBoot를 이용한 RESTful Web Service 개발
    springboot
    넥사크로
    에러해결
    토이프로젝트
    MSA
    QueryDSL
    egov
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
데부한
JPA 프록시와 연관관계 - 즉시 로딩과 지연 로딩
상단으로

티스토리툴바