JPA 기본 | 즉시 로딩과 지연 로딩

2024. 7. 13. 21:36· 인프런 김영한 강의 정리/자바 ORM 표준 JPA 프로그래밍 기본편
목차
  1. 1. 지연 로딩, 즉시 로딩 개념
  2. 2. 지연 로딩(fetch = FetchType.LAZY)
  3. 3. 즉시 로딩(fetch = FetchType.EAGER)
  4. 4. 프록시와 즉시로딩 주의
728x90

즉시 로딩과 지연 로딩을 이해하기 위해 프록시 개념을 먼저 이해해야한다.

 

JPA 기본 | 프록시 (즉시 로딩, 지연 로딩을 이해하기 위한)

1. 프록시란?JPA에서 연관된 엔티티를 지연 로딩하기 위해 사용되는 객체다.데이터베이스 접근을 지연시켜 성능을 최적화한다.필요한 시점까지 데이터 로딩을 미뤄서 메모리 사용량을 줄이고 불

ururuwave.tistory.com

그래서 이전에 포스팅한 프록시를 첨부한다.

 

1. 지연 로딩, 즉시 로딩 개념

- 지연 로딩 : 연관된 엔티티를 실제로 접근할 때까지 데이터베이스 조회를 지연시키는 로딩 전략

- 즉시 로딩 : 연관된 엔티티를 함께 조회하여 즉시 데이터베이스에서 로드하는 로딩 전략

 

2. 지연 로딩(fetch = FetchType.LAZY)

Member 엔티티와 Team 엔티티는 양방향 연관관계를 맺고 있다.

지연 로딩 설정을 해주지 않으면, 단순히 Member 정보만 사용할 때도 Team을 조인하는 쿼리가 실행된다.

조인 없이 Member 테이블만 조회하고 싶을 때, 지연 로딩을 사용할 수 있다.

그리고 Team과 관련된 정보를 조회하면 그때 Team 프록시 객체가 초기화 된다. (Team 조회쿼리 실행 시점)

 

✅코드 예시

@Entity
@Getter
@Setter
public class Member extends BaseEntity{

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

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


@Entity
@Getter
@Setter
public class Team extends BaseEntity{

    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>(); //ArrayList<>()로 초기화한다. add할때 NullPointException이 뜨지 않게!

}

(fetch = FetchType.LAZY) 를 붙여준다.

이렇게 하면 Member 엔티티에 Team 필드가 지연로딩으로 설정된다.

 

✅main 메서드

public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{

            // Team 엔티티 생성 및 저장
            Team team = new Team();
            team.setName("teamA");
            em.persist(team);

            // Member 엔티티 생성 및 저장
            Member member = new Member();
            member.setUsername("ururuwave");
            member.setTeam(team); //연관관계 설정
            em.persist(member);


            // DB 반영 및 영속성 컨텍스트 초기화
            em.flush();
            em.clear();

            Member findMember = em.find(Member.class, member.getId());
            
            System.out.println("findMember = " + findMember.getClass()); //실제 엔티티
            System.out.println("findMember = " + findMember.getTeam().getClass()); //프록시 객체

            System.out.println("=====team 조회 START=====");
            System.out.println("findMember = " + findMember.getTeam().getName());
            System.out.println("=====team 조회 END=====");

            tx.commit();
        }catch (Exception e){
            tx.rollback();
            e.printStackTrace(); //예외 출력
        }finally {
            em.close();
        }
    }

✔️ Member 엔티티에 지연로딩 설정을 해주고나면 Team 프록시 객체를 생성할 수 있다.

- em.find()로 findMember 인스턴스를 생성하면 Member만 조회하는 쿼리가 실행된다.

findMember는 실제 Member 엔티티고 지연 로딩 설정된 findMember의 team은 Team 프록시 객체다.

 

✔️Team의 정보를 조회하려고 하는 시점에서 Team 프록시 객체가 초기화 된다.

 

✅콘솔 확인

select
        m1_0.MEMBER_ID,
        m1_0.createdBy,
        m1_0.createdDate,
        m1_0.lastModifiedBy,
        m1_0.lastModifiedDate,
        m1_0.TEAM_ID,
        m1_0.USERNAME 
    from
        Member m1_0 
    where
        m1_0.MEMBER_ID=?
findMember = class hellojpa.Member
findMember.getTeam() = class hellojpa.Team$HibernateProxy$rWCuhsMX
=====team 조회 START=====
select
        t1_0.TEAM_ID,
        t1_0.createdBy,
        t1_0.createdDate,
        t1_0.lastModifiedBy,
        t1_0.lastModifiedDate,
        t1_0.name 
    from
        Team t1_0 
    where
        t1_0.TEAM_ID=?
findMember = teamA
=====team 조회 END=====

Member 테이블만 조회하는 쿼리가 실행됐다.

findMember는 실제 Member 엔티티다.

findMember.getTeam()은 Team 프록시 객체다.

Team을 사용하려는 시점에서 Team 테이블을 조회한다. 

 

3. 즉시 로딩(fetch = FetchType.EAGER)

비즈니스 로직상 Member와 Team을 함께 사용하는 빈도가 높다면 즉시 로딩을 하는 것이 효율적이다.

 

✅코드 예시

(fetch = FetchType.EAGER)

 

Member 엔티티의 (fetch = FetchType.LAZY) 부분만 EAGER로 바꿔주면 된다.

 

✅콘솔 확인

select
        m1_0.MEMBER_ID,
        m1_0.createdBy,
        m1_0.createdDate,
        m1_0.lastModifiedBy,
        m1_0.lastModifiedDate,
        t1_0.TEAM_ID,
        t1_0.createdBy,
        t1_0.createdDate,
        t1_0.lastModifiedBy,
        t1_0.lastModifiedDate,
        t1_0.name,
        m1_0.USERNAME 
    from
        Member m1_0 
    left join
        Team t1_0 
            on t1_0.TEAM_ID=m1_0.TEAM_ID 
    where
        m1_0.MEMBER_ID=?
findMember = class hellojpa.Member
findMember.getTeam() = class hellojpa.Team
=====team 조회 START=====
teamName = teamA
=====team 조회 END=====

처음에 MEMBER, TEAM테이블을 조인하여 한꺼번에 데이터베이스를 조회한다.

지연 로딩했던 때와 달리 Team은 프록시 객체가 아닌 실제 Team 엔티티 객체다.

team 조회를 했을 때, TEAM 테이블을 조회할 필요없이 바로 teamName을 가져올 수 있다.

 

4. 프록시와 즉시로딩 주의

✔️실무에서는 모든 연관관계에 지연 로딩만 사용한다.

왜? 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다. 

연관 매핑되어있는게 2개가 아닌 5개, 10개라면 무조건 조인이 발생하니 성능 저하 이슈가 생긴다.

 

✔️즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.

N+1 문제란, 주 엔티티를 1회 조회하고 연관된 엔티티의 갯수(N)만큼 추가 쿼리가 실행되는 문제다.

 

✅코드 예시

public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{

            // Team 엔티티 생성 및 저장
            Team team = new Team();
            team.setName("teamA");
            em.persist(team);

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

            // Member 엔티티 생성 및 저장
            Member member = new Member();
            member.setUsername("ururuwave");
            member.setTeam(team); //연관관계 설정
            em.persist(member);

            Member member2 = new Member();
            member2.setUsername("member2");
            member2.setTeam(teamB); //연관관계 설정
            em.persist(member2);


            // DB 반영 및 영속성 컨텍스트 초기화
            em.flush();
            em.clear();

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

            //SQL : select * from member
            //SQL : select * from team where TEAM_ID = ***


            tx.commit();
        }catch (Exception e){
            tx.rollback();
            e.printStackTrace(); //예외 출력
        }finally {
            em.close();
        }
    }

Member와 Team 인스턴스를 2개씩 만들었다.

JPQL을 사용하여 members 리스트를 가져왔다.

원래 의도는 select * from member 쿼리를 한번만 실행하는 것이다.

그런데 즉시 로딩으로 설정되어있다면 N개의 member 엔티티에 갯수만큼 team을 조회하는 쿼리가 N번 실행된다.

N+1문제는 데이터베이스에 너무 많은 쿼리를 발생시켜 성능을 저하 시킨다.

(*N+1 문제 해결법은 다른 포스팅에서 다룰 예정이지만, 즉시로딩을 사용하지 않고 한꺼번에 조회하는 쿼리를 실행하고 싶을 때 페치 조인을 사용하면 된다.)

 

✔️@ManyToOne, @OneToOne은 기본이 즉시 로딩이다. 그러니 LAZY로 설정해야한다.

✔️ @OneToMany, @ManyToMany는 기본이 지연 로딩이다.

728x90

'인프런 김영한 강의 정리 > 자바 ORM 표준 JPA 프로그래밍 기본편' 카테고리의 다른 글

JPA 기본 | 데이터 타입(1) - 기본 값과 임베디드 값 타입  (0) 2024.07.14
JPA 기본 | 영속성 전이(CASCADE)와 고아 객체  (0) 2024.07.13
JPA 기본 | 프록시 (즉시 로딩, 지연 로딩을 이해하기 위한)  (0) 2024.07.13
JPA 기본 | @MappedSuperclass  (0) 2024.07.10
JPA 기본 | 상속관계 매핑  (0) 2024.07.10
  1. 1. 지연 로딩, 즉시 로딩 개념
  2. 2. 지연 로딩(fetch = FetchType.LAZY)
  3. 3. 즉시 로딩(fetch = FetchType.EAGER)
  4. 4. 프록시와 즉시로딩 주의
'인프런 김영한 강의 정리/자바 ORM 표준 JPA 프로그래밍 기본편' 카테고리의 다른 글
  • JPA 기본 | 데이터 타입(1) - 기본 값과 임베디드 값 타입
  • JPA 기본 | 영속성 전이(CASCADE)와 고아 객체
  • JPA 기본 | 프록시 (즉시 로딩, 지연 로딩을 이해하기 위한)
  • JPA 기본 | @MappedSuperclass
백엔드 개발자 - 젤리곰
백엔드 개발자 - 젤리곰
오늘도 배움이 있는 하루가 되길 바라는 개발자
백엔드 개발자 - 젤리곰
backend-gummyBear
백엔드 개발자 - 젤리곰
전체
오늘
어제
  • 분류 전체보기 (144)
    • 인프런 김영한 강의 정리 (60)
      • 스프링 핵심원리 기본편 (12)
      • 모든 개발자를 위한 HTTP 웹 기본 지식 (10)
      • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (3)
      • 자바 ORM 표준 JPA 프로그래밍 기본편 (28)
      • 실전! Querydsl (6)
    • Spring (2)
    • 프로젝트일지 (6)
    • 프로그래밍 언어 (20)
      • Java (17)
      • JavaScript (3)
      • Python (0)
    • 데이터베이스 (4)
      • Oracle (2)
      • ORM (1)
      • SQL 튜닝 (1)
    • 형상관리 (1)
      • Git (0)
    • 알고리즘&자료구조 (34)
      • Algorithm (31)
      • Data Structure (1)
    • CS지식 (4)
    • Cloud (5)
    • 일기 (7)
      • 공부 일기 (3)
      • 독서 일기 (2)
      • 마음 일기 (2)

블로그 메뉴

  • 홈
  • 태그

공지사항

인기 글

태그

  • 인프콘
  • 커스텀annotation
  • SublimeText단축키
  • ORM프레임워크
  • LeetCode17번
  • 스프링컨텍스트
  • 데이터베이스정규화
  • #{}와${}의차이
  • 인터페이스
  • 프론트엔드개발자업무
  • 객체지향방법론
  • 프론트엔드역사
  • LeetCode200번
  • dfs알고리즘
  • 클라이언트서버통신
  • jquery와javascript
  • 객체지향의사실과오해
  • 업캐스팅
  • 다운캐스팅
  • 힙자료구조

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
백엔드 개발자 - 젤리곰
JPA 기본 | 즉시 로딩과 지연 로딩
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.