슬기로운 개발생활

[JPA] 즉시로딩과 지연로딩 알아보기(FetchType.EAGER, LAZY)

by coco3o
반응형

JPA에서 연관관계를 조회할 때 참조하는 객체들의 조회 시점을 선택할 수 있도록

두 가지 방법을 제공하는데 바로 즉시 로딩(EAGER Loading)지연 로딩(LAZY Loading)이다.

간단한 예제를 통해 알아보자.


Member, Board Entity Class

@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String email;

    private String password;
}

@ToString(exclude = "member")
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Entity
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    @ManyToOne
    private Member member;
}

위 코드에서 Board(게시글)와 Member(회원)는 @ManyToOne(N:1)의 관계를 맺고 있다.

 

즉, 한 명의 회원(1)은 여러 게시글(N)을 작성할 수 있다는 뜻이다.

 

따라서 게시글의 기준으로 봤을 때 회원과의 관계는 N:1이 된다.

 

만약 Board 엔티티를 조회한다면 User의 엔티티도 함께 조회가 될 것이다.

각 연관관계의 default 속성은 다음과 같다.
@ManyToOne : EAGER
@OneToOne : EAGER
@ManyToMany : LAZY
@OneToMany : LAZY

테스트 해보기

@Test
void 데이터_조회() {
    /* given */
    Optional<Board> result = boardRepository.findById(1L);
    /* when */
    Board board = result.get();
    /* then */
    System.out.println(board);
}

EAGER

실행된 SQL 쿼리문을 보면 board 테이블 외에 member 테이블도 함께 조회하며, outer join으로 처리되는 것을 볼 수 있다.

※ '즉시 로딩'은 항상 외부 조인(OUTER JOIN)을 사용한다. ( 외부 조인보다 내부 조인(INNER JOIN)이 성능 최적화에 더 유리하다. )

 

위 결과와 같이 특정 엔티티를 조회할 때 연관된 모든 엔티티를 같이 로딩하는 것즉시 로딩(EAGER Loading)이라고 한다.

 

즉시 로딩은 연관된 엔티티를 모두 가져온다는 장점이 있지만,

실무에서 엔티티 간의 관계가 복잡해질수록 조인으로 인한 성능 저하를 피할 수 없고 JPQL에서 N + 1 문제를 일으킨다.

 

'즉시 로딩'은 불필요한 조인까지 포함해 처리하는 경우가 많기 때문에 '지연 로딩'의 사용을 권장하고 있다.

※'지연 로딩'을 기본으로 사용하고, 상황에 맞게 사용하자.


지연 로딩(Lazy Loading)

@ToString(exclude = "member")
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Entity
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;
    
    /* lazy loading 사용 */
    @ManyToOne(fetch = FetchType.LAZY)
    private Member member;
}

Board 엔티티에 지연 로딩을 적용하고 SQL 쿼리문을 확인해보면 이전과는 다르게 Board 테이블만 조회된다.

 

하지만, 지연 로딩(LAZY) 적용 상태에서 Board의 Member를 접근하려 하면 다음과 같은 오류가 발생한다.

    @Test
    void 데이터_조회() {
        Optional<Board> result = boardRepository.findById(1L);

        Board board = result.get();

        System.out.println(board);
        System.out.println(board.getMember()); //Error
    }

proxy [~] - no Session 은 DB와 연결된 Connection이 없어서 나는 에러 메시지이다. 

( 정확히는 커넥션이 없기보다. 이미 커넥션에 커밋을 날리고 트랜잭션이 닫힌 상태를 의미한다. )

 

이러한 문제를 해결하기 위해서는 데이터베이스와의 재연결이 필요한데, @Transactional 어노테이션을 통해 해결할 수 있다.

 

@Transactional 어노테이션은 해당 메소드를 하나의 '트랜잭션'으로 처리하라는 의미이다.

트랜잭션으로 처리하면 필요할 때 다시 데이터베이스와의 연결이 생성되기 때문에 테스트는 정상적으로 실행될 것이다.

    @Transactional
    @Test
    void 데이터_조회() {
        Optional<Board> result = boardRepository.findById(1L);

        Board board = result.get();

        System.out.println(board);
        System.out.println(board.getMember());
    }


정리하면

  • 가급적이면 지연 로딩(LAZY Loading)만 사용하자.
  • 즉시 로딩(EAGER Loading)을 적용하면 예상하지 못한 SQL이 발생할 수 있다.
  • 즉시 로딩(EAGER Loading)은 JPQL에서 N+1 문제를 일으킨다.
반응형

블로그의 정보

슬기로운 개발생활

coco3o

활동하기