JPA 를 사용하면서 지연로딩과 즉시로딩에 대한 개념을 알고 있었다.
근데 얘네 덕분에 N+1이 발생한다는 사실을 알았다.
사실 n+1에 대해서만 쪼오오끔 알구.. 쿼리dsl로 넘어 갔었어서 정확히 몰랐었다 ㅎㅎ… 이번 프로젝트에소 JPA 위주로 사용하니까 짚고 가려고 한다
즉시로딩
@Entity
public class Board {
@Id
private Long id;
}
@Entity
public class Post {
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "board_id")
private Board board;
}
이런 식의 엔티티들이 있다고 치자
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public void getPost(Long postId) {
Post post = postRepository.findById(postId);
}
}
여기서 post를 조회하는 쿼리는 어떻게 나갈까?
select
p, b
from
Post p
inner join
p.team t
where p.id = :postId
이런 식으로 나간다.
어.. 나는 포스트만 조회하고 싶은데... 필요 없는 Team 까지 join을 해서 불러온다.
즉 로딩시간의 낭비가 조금이라도 생기는 것
이게 즉시로딩이다
지연로딩
그럼 FetchType Lazy를 설정하자!! 이게 지연로딩을 할 수 있게 해준다.
@Entity
public class Board {
@Id
private Long id;
}
@Entity
public class Post {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
private Board board;
}
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public void getPost(Long postId) {
Post post = postRepository.findById(postId);
}
}
이 findById 메서드를 호출하면 쿼리는 어떻게 나갈까?
select
p
from
Post p
where p.id = :postId
이런 식으로 나가게 된다.
post에 있던 board 까지 안불러 온다는 것
와우 그럼 해결 됐다!!! 룰루랄라
할 수 있는데
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public void getPost(Long postId) {
Post post = postRepository.findById(postId);
Board board = post.getBoard(); // getter 가 있다고 가정
}
}
이렇게 board 를 불러오는 코드를 추가하면
select
p, b
from
Post p
inner join
p.team t
where p.id = :postId
다시 이런 쿼리가 나가게 된다.
만약에 Post를 여러개 조회하고 싶으면?
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public void getPostList() {
List<Post> postList = postRepository.findAll();
// Board board = post.getBoard(); // getter 가 있다고 가정
}
}
이건 Lazy를 설정했기에 쿼리가 한번만 나간다.
근데 Post 안에 있는 Board 를 불러오고 싶다면?
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public void getPostList() {
List<Post> postList = postRepository.findAll();
for (Post post : PostList) {
post.getBoard();
}
}
}
post의 갯수만큼 추가적인 쿼리가 계속 나간다.
N+1 문제가 발생하는 것이다.
여기까지 정리
1. JPA는 기본적으로 연관관계에 있는 모든 엔티티를 조회할 수 있게 쿼리를 날리려고 한다.
2. 그래서 실무에서는 쿼리의 성능을 위해 FetchType을 기본적으로 Lazy로 설정한다.
3. 근데 단일 엔티티만 끌고 온 상황에서, 참조를 조회하고 싶으면 N+1 이 발생한다.
어떻게 해결?
Fetch join 을 사용하면된다.
Fetch join은 해당 엔티티의 연관관계를 모두 불러오게 해준다.
아 그러면 엔티티 단일만 필요할때는 Fetch join 사용하지 말고
엔티티 연관관계를 써야 되는 메서드는 Fetch join 하면 되겠다
라고 생각하면 된다.
public interface PostRepository extends JPARepository<Post, Long> {
@Query("selet p from Post p join fetch p.board b where p.id = :postId")
Post findByIdFetch(@Param("postId") Long id);
}
음.. 이런 식으로 작성하면 된다.
다른 방법도 있는데, Batch Size나 등등.. 근데 결국 fetch 를 많이 사용한다고 한다.
'TIL' 카테고리의 다른 글
TIL 2024-07-25 값 객체란 무엇일까 (2) | 2024.07.25 |
---|---|
TIL 2024-07-24 도메인 주도 설계란 무엇일까 (1) | 2024.07.24 |
TIL 2024-03-01 간단한 BufferedReader 와 StringTokenizer 사용법 (0) | 2024.03.01 |
TIL 2024-02-26 TCP/IP 인터넷 계층, 링크 계층 (0) | 2024.02.27 |
TIL 2024-02-25 TCP 연결 성립 / 연결 해제 과정 (0) | 2024.02.25 |