N+1
문제는 JPA와 hibernate에만 특정되는 문제는 아니고, data access technology 를 쓸 때 발생할 수 있는 문제
parent entity를 받기 위한 쿼리 1개 + child entities를 받기 위한 쿼리 N개
@Entity
@Table
public class Post{
@Id
private Long id;
private String content;
@OneToMany(mappedBy = "post")
private Set<Comment> comments;
}
@Entity
public class Comment{
@Id
private Long id;
private String content;
}
select * from post;
-> post를 select하고 각 record에 있는 post_id를 가지고 comment 테이블 가서 select을 또 한다.
parent table에서 select 된 수를 의미한다.
public interface PostRepository extends CrudRepository<Post, Long> {
// 1. N+1 문제가 database level에서 발생
List<Post> findAllBy();
}
fetch join
사용해서 해결@Query("SELECT p FROM Post p LEFT JOIN FETCH p.comments")
List<Post> findWithFetchJoin();
EntityGraph
사용해서 해결 - 권장하지 않음@EntityGraph(attributePaths = {"comments"})
List<Post> findAll();
@BatchSize
@Entity
@Table
public class Post{
...
@OneToMany(mappedBy = "post")
@BatchSize(size = 10)
private Set<Comment> comments;
}
-> select * from comment where post_id in (?, ?, ?, ...) 이런 식으로 쿼리가 날라가서 n번 발생할 걸 n/batch_size 번 발생하게 하는 것이다.
application.yml
파일에서 bech size를 정할 수도있다.spring.jpa.properties.hibernate.default_batch_fetch_size: 1000
FetchMode.SUBSELECT
@Entity
@Table
public class Post{
...
@OneToMany(mappedBy = "post")
@Fetch(FetchMode.SUBSELECT)
private Set<Comment> comments;
}
SELECT * from posts;
SELECT * from comments
where post_id in (select id from posts);
-> complex한 쿼리일 경우 성능이 떨어진다. 따라서 filter
와 page
가 필요.., 딱 필요한 id만 두번째 쿼리에 넘겨져야 한다.