✏️ JPA 즉시 로딩과 지연 로딩

2026. 7. 22. 00:49·Backend/JPA

이 글은 Velog에서 이전한 글입니다. Velog 원문 보기

⭐️ 즉시 로딩과 지연 로딩

JPA에서는 데이터를 조회할 때 즉시 로딩(EAGER)과 지연 로딩(LAZY) 두 가지 방식이 있다. 이 두 가지 방식을 간단하게 설명하면 즉시 로딩은 데이터를 조회할 때 연관된 데이터까지 한 번에 불러오는 것이고, 지연 로딩은 필요한 시점에 연관된 데이터를 불러오는 것이라고 할 수 있다.


우선 위 사진의 상황을 가정하고 엔티티를 작성해보자.

Member Entity

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

Team Entity와 결과 쿼리

@Entity
public class Team {

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

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

관계를 설정하자면 Member, Team Entity는 양방향 연관관계이고, 연관관계의 주인은 Member이다.

여기서 질문이 있다.
멤버를 조회할 때 팀도 함께 조회해야 할까?
단순히 멤버 정보만 사용하는 비즈니스 로직일 때는 멤버를 조회할 때 팀까지 Join해서 가지고 오게 되면 손해가 아닐까?

그래서 JPA는 지연 로딩이라는 옵션을 제공한다.

📌 지연 로딩(LAZY)

  • @xxToxx(fetch = fetchType.LAZY)

지연 로딩 LAZY을 사용하서 프록시로 조회

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @ManyToOne(fetch = FetchType.LAZY) //proxy 객체로 조회(Member 클래스만 db에서 조회)
    @JoinColumn(name = "TEAM_ID")//Fk, 외래키
    private Team team; //연관관계의 주인
}

@ManyToOne에다가 fetch = FetchType.LAZY을 추가했다. fetch 타입을 LAZY로 하면 Member를 조회할 때는 Member만 DB에서 조회해야 한다.
코드를 실행해서 확인해보자.

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

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

em.flush(); //영속성 컨텍스트의 1차 캐시에 있는 정보를 db로 강제 전송
em.clear(); //영속성 컨텍스트 초기화

Member m = em.find(Member.class, member.getId());

System.out.println("m: " + m.getTeam().getClass());

tx.commit(); 


----------------------------------------------
## 결과 ##
Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_3_0_,
        member0_.INSERT_MEMBER as insert_m2_3_0_,
        member0_.createdDate as createdd3_3_0_,
        member0_.UPDATE_MEMBER as update_m4_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=?
m: class hellojpa.Team$HibernateProxy$rNT34pzA

지연로딩으로 설정한 뒤 Member를 조회해보면 Member를 조회하는 쿼리만 나가고 Team을 조회하는 쿼리가 나가지 않았다.
그리고 m.getTeam().getClass()에서 Team은 프록시 객체인 것을 알 수 있다.

실제 Team을 사용하는 시점에 프록시 객체가 초기화되면서 DB에서 Team과 관련된 데이터를 가져온다.

지연 로딩으로 설정하면 연관된 것을 Proxy로 가져온다.


사진으로 보면 이해가 쉽다. 내부 메커니즘이 member1을 로딩 할 때 Team Entity 인스턴스는 지연 로딩으로 세팅이 되어 있어서 Proxy로 가져온 것이다.

  1. em.find(Member.class, 1L);로 멤버 객체를 가져옴.
  2. Member와 Team이 지연 로딩으로 설정되어 있으면 Team의 가짜 프록시 객체를 받아 놓는다.
  3. Member에서 Team을 가져와서 팀의 어떤 값을 실제 사용하는 시점에 DB 초기화
  4. 쿼리 발생

팀을 가져올 때가 아닌 팀에 있는 무언가를 실제 사용할 때 초기화되는 것이다.

전체 애플리케이션에서 대부분 멤버를 조회했을 때 멤버만 사용하고 팀을 거의 사용하지 않는다면 LAZY를 사용하는 것이 효율적이다.
그런데 반대로 비즈니스 로직의 거의 대부분이 멤버랑 팀을 같이 사용한다면?

📌 즉시 로딩(EAGER)

  • @xxToxx(fetch = fetchType.EAGER)
    즉시 로딩은 엔티티를 조회할 때 처음부터 연관관계에 있는 것을 모두 Join하는 것이다.
    코드로 알아보자.

Member Entity

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "TEAM_ID")//Fk, 외래키
    private Team team; //연관관계의 주인
}

Team Entity와 결과 쿼리

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

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

em.flush(); //영속성 컨텍스트의 1차 캐시에 있는 정보를 db로 강제 전송
em.clear(); //영속성 컨텍스트 초기화

Member m = em.find(Member.class, member.getId());

System.out.println("m: " + m.getTeam().getClass());

tx.commit(); 

----------------------------------
## 결과 ##
Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_3_0_,
        member0_.INSERT_MEMBER as insert_m2_3_0_,
        member0_.createdDate as createdd3_3_0_,
        member0_.UPDATE_MEMBER as update_m4_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_.INSERT_MEMBER as insert_m2_5_1_,
        team1_.createdDate as createdd3_5_1_,
        team1_.UPDATE_MEMBER as update_m4_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=?

m: class hellojpa.Team

fetch를 EAGER로 설정하고 결과 쿼리를 보니까 LAZY와 다른 점이 보인다. 쿼리가 Member를 조회할 때 처음부터 Member와 Team을 Join한다. 이때 프록시 객체로 가져오는 것이 아닌 진짜 엔티티 객체로 가져온다.

애플리케이션 개발을 했는데 대부분의 경우 Member을 사용할 때 무조건 Team을 같이 사용한다면 즉시 로딩을 사용하는게 유리해보인다.

하지만 실무에서는 즉시 로딩을 사용하면 안된다.
왜? 한 번에 조인 쿼리로 가져오면 편하고 좋을 거 같은데 왜 사용하면 안될까?

  • 즉시 로딩을 사용하면 전혀 예상하지 못한 SQL이 막 나간다.
    • 위의 예시처럼 Member, Team 테이블만 있다면 괜찮지만 테이블이 10개, 100개가 된다면 find할 때 연관관계로 이어져있는 모든 테이블이 join된다.
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
    • em.find는 Pk를 찍어서 가져오는 것이기 때문에 JPA가 내부적으로 최적화를 할 수가 있다. 그런데 JPQL은 SQL로 번역이 되기에 최적화가 안된다.
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩(EAGER) -> 지연 로딩(LAZY)로 설정
  • @OneToMany, @ManyToMany는 기본이 지연 로딩(LAZY)

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

게시글 속 자료는 모두 위 강의 속 자료를 사용했습니다.


https://ict-nroo.tistory.com/131

'Backend > JPA' 카테고리의 다른 글

✏️ JPA 값 타입  (0) 2026.07.22
✏️ JPA 영속성 전이(CASCADE), 고아 객체  (0) 2026.07.22
✏️ JPA 프록시(Proxy)  (0) 2026.07.22
✏️ JPA 고급 매핑  (0) 2026.07.22
✏️ JPA 연관 관계 (2)  (0) 2026.07.21
'Backend/JPA' 카테고리의 다른 글
  • ✏️ JPA 값 타입
  • ✏️ JPA 영속성 전이(CASCADE), 고아 객체
  • ✏️ JPA 프록시(Proxy)
  • ✏️ JPA 고급 매핑
pp8817
pp8817
공부한 내용, 개발 관련 지식, 트러블 슈팅 등을 기록합니다. 이전 블로그: https://velog.io/@pp8817/posts
  • pp8817
    끄적이는 개발 log
    pp8817
  • 전체
    오늘
    어제
    • 분류 전체보기 (273)
      • Project (71)
        • 척척학사 (29)
        • Book (23)
        • Saynow (3)
        • YAPP 27기 (4)
        • 나의 작은 프로젝트 (10)
        • Landit (2)
      • Backend (88)
        • Spring (13)
        • Spring MVC (13)
        • Spring Security (4)
        • JPA (26)
        • Database (19)
        • HTTP·Web (13)
        • Architecture (0)
      • Language·CS (59)
        • Java·Kotlin (5)
        • CS Interview (17)
        • Backend Interview (5)
        • Concepts (32)
      • Algorithm (25)
        • Algorithm (24)
        • 소마 알고리즘 스터디 (1)
      • Infra (8)
      • Troubleshooting (11)
      • Retrospective (6)
      • Etc (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    HTTP WEB 기본 지식
    나의 작은 프로젝트
    척척학사
    BOJ
    CS Interview
    interview
    Project
    jpa
    OS
    Python
    java
    Algorithm
    게시판
    트러블슈팅
    object
    Spring
    Book
    Spring MVC
    개념 정리!
    http
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
pp8817
✏️ JPA 즉시 로딩과 지연 로딩
상단으로

티스토리툴바