현재 Mlog의 서비스 코드 중 아래와 같이 작성된 코드가 존재한다.
/**
* 포스트 목록
* */
@Transactional(readOnly = true)
public List<PostDto.ListDto> getPostList() {
return postRepository.findAllByVisibleIsTrueOrderByIdDesc()
.stream().map(Post::toListDto)
.toList();
}
처음에 이 코드를 작성할 때 @Transactional(readOnly = true)
어노테이션을 작성하지 않았다.
하지만 Post
와 연관 관계인 PostSeries
를 Lazy Loading을 하려는데 이미 세션이 사라져 LazyInitialization
오류가 발생하였다.
OSIV 설정은 false로 되어있었고, 해결 방법은 아래 세가지가 있었다.
@Transactional(readOnly = true)
어노테이션을 붙여 읽기 전용 트랜잭션을 설정하는 것이 코드를 작성할 때 나는 2번 방법을 선택하였다. 이유는 알고 있던 방법이 2번 뿐이라 그랬었다.
하지만, 코드를 하나하나 이유를 따져가며 다시 확인해보니 이 코드를 왜 이렇게 작성하였을까에 대한 의문이 생겼다.
왜? 라는 질문을 던졌을 때 대답할 수 없었기 때문에 이 방법이 좋은 방법인지에 대한 근거도, 확신도 없었다.
지연 로딩을 즉시 로딩을 하게 되면, 어차피 날릴 쿼리를 바로 날리게 된다.
생각해보면 1번 방법과 2번 방법의 차이가 크게 없다고 생각한다.
결론적으로 두 방법 모두 아래처럼 쿼리를 발생시키기 때문이다.
Hibernate: # 1개
select
p1_0.id,
p1_0.content,
p1_0.post_series,
p1_0.preview_content,
p1_0.thumbnail,
p1_0.title,
p1_0.visible,
p1_0.writing_time
from
post p1_0
where
p1_0.visible
order by
p1_0.id desc
Hibernate: # 2개
select
p1_0.id,
p1_0.series
from
post_series p1_0
where
p1_0.id=?
Hibernate: # 3개
select
p1_0.id,
p1_0.series
from
post_series p1_0
where
p1_0.id=?
Hibernate: # 4개
select
p1_0.id,
p1_0.series
from
post_series p1_0
where
p1_0.id=?
Hibernate: # 5개
select
p1_0.id,
p1_0.series
from
post_series p1_0
where
p1_0.id=?
이 방법은 지연 로딩으로 인한 두번째 쿼리를 어느 시점에 날리느냐에 대한 차이만 존재한다.
Post를 조회 후 DTO로 변환하는 과정에서 PostSeries의 시리즈명을 조회하는데에서만 차이가 존재하고 즉시 로딩처럼 두개의 쿼리가 발생하게 된다.
추가적으로 조회 쿼리를 명시적으로 표시할 수 있는 이점도 존재한다.
이 방법은 개인적으로 굳이? 싶었던 방법이다.
Controller단까지 트랜잭션을 할 필요가 전혀 없다고 생각하였기 때문이다.
결론적으로 세가지 방법 중 가독성을 높여줄 수 있는 2번 방법이 괜찮다고 생각하였다.
하지만 진짜 문제는 따로 있었다.
바로 N + 1 문제다.
N + 1 문제는 추후에 다시 다뤄보도록 하겠다.