엔티티를 조회할 때 연관된 엔티티를 조회하기 위한 N번의 쿼리가 추가적으로 발생하는 문제 ⇒ DB 부담 증가
1:N 뿐만 아니라 1:1, N:1 관계에서 모두 발생할 수 있음!
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
// ...
@Builder.Default
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Image> images = new ArrayList<>(); // image는 id와 url을 갖고 있음
//...
}
@Transactional(readOnly = true)
public PostResponse getPostList(Long postId) {
List<Post> posts = postRepository.findAll();
return posts.stream()
.map(PostResponse::toResponse)
.collect(Collectors.toList());
}
Hibernate: select p1_0.id, p1_0.content, p1_0.title from post p1_0
Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?
Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?
Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?
Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?
Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?
객체는 레퍼런스를 가지고 언제든지 연관된 객체에 접근할 수 있지만, RDB의 경우 SELECT 쿼리를 통해서만 조회할 수 있기 때문에 연관된 엔티티를 조회하려고 할 때 추가적으로 쿼리가 발생하게 된다.
Q. 지연 로딩이 아닌 즉시 로딩을 사용하면 되는 것 아닌가요?
@OneToMany, @ManyToMany는 지연(Lazy) 로딩 이 기본
@ManyToOne, @OneToOne은 즉시(Eager) 로딩 이 기본
⇒ JPQL을 사용하는 시점에 N+1 문제 발생
+ 예상치 못한 쿼리 발생 우려가 있어서 실무에서 사용하지 않음,
public interface PostRepository extends JpaRepository<Post, Long> {
@Override
@Query("select p from Post p join fetch p.images")
List<Post> findAll();
}
Hibernate:
select
p1_0.id,
p1_0.content,
i1_0.post_id,
i1_0.id,
i1_0.url,
p1_0.title
from
post p1_0
join
image i1_0
on p1_0.post_id=i1_0.post_post_id
cf) 하이버네이트 6 이후 부터는 자동으로 중복 제거(distinct)
select p from Post p join p.images
fetch join을 편하게 사용하도록 도와주는 기능
public interface PostRepository extends JpaRepository<Post, Long> {
@Override
@EntityGraph(attributePaths = {"images"})
List<Post> findAll();
}
기본적으로 inner join을 사용하는 fetch join과 다르게 EntityGraphs는 outer join을 사용하기 때문에 성능 이슈가 있을 수 있음
@BatchSize(size = 5)
@Entity
public class Image {
...
}
@Entity
public class Post {
@BatchSize(size = 5)
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Image> images = new ArrayList<>();
}
Hibernate: select p1_0.id, p1_0.content, p1_0.title from post p1_0
Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.post_id in (?, ?, ?, ?, ?)
Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.post_id in (?, ?, ?, ?, ?)
전역적으로 Batch Size 설정하는 방법
// application.yml
jpa:
properties:
hibernate:
default_batch_fetch_size: 100
https://www.inflearn.com/course/ORM-JPA-Basic
https://inma.tistory.com/165
https://ttl-blog.tistory.com/1135
https://velog.io/@xogml951/JPA-N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC