[JPA] 즉시로딩과 지연로딩 알아보기(FetchType.EAGER, LAZY)
by coco3oJPA에서 연관관계를 조회할 때 참조하는 객체들의 조회 시점을 선택할 수 있도록
두 가지 방법을 제공하는데 바로 즉시 로딩(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);
}
실행된 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 문제를 일으킨다.
'🌈Programming > JPA' 카테고리의 다른 글
[JPA] N+1 문제 원인 및 해결방법 알아보기 (1) | 2022.03.03 |
---|---|
[JPA] JPQL(Java Persistence Query Language)이란? (0) | 2022.01.20 |
[JPA] 양방향 순환참조 문제 및 해결방법 (0) | 2022.01.05 |
[JPA] 연관관계 매핑 알아보기 (@ManyToOne, @OneToMany, @OneToOne, @ManyToMany) (1) | 2021.09.23 |
[JPA] querydsl 설정하기 (0) | 2021.08.05 |
블로그의 정보
슬기로운 개발생활
coco3o