scope와 N+1 문제
1. scope
page (pageContext)수명: 하나의 JSP 페이지가 실행되는 동안에만 유지됩니다.
특징: 페이지가 바뀌면(포워드, 리다이렉트 모두) 데이터가 사라집니다. 오직 해당 파일 안에서만 임시로 쓸 변수를 담습니다.
request수명: 클라이언트의 HTTP 요청 한 번이 처리되어 응답이 나갈 때까지 유지됩니다.
특징: 서블릿에서 데이터를 담아 JSP로 화면을 넘겨줄 때(Forward) 가장 많이 씁니다.
session수명: 웹 브라우저를 켜고 끌 때까지, 혹은 세션이 만료될 때까지 유지됩니다.
특징: 로그인한 유저의 정보처럼 브라우저를 닫기 전까지 계속 유지해야 하는 데이터를 담습니다.
- 기본유지시간:30분
application수명: 웹 서버(톰캣 등)가 켜져서 꺼질 때까지 애플리케이션 전체에 유지됩니다.
특징: 모든 사용자와 모든 페이지가 공유하는 전역 데이터(예: 전체 방문자 수)를 담을 때 씁니다.
2. N+1 문제 : 개발자가 코드를 짤 때는 분명 boardRepository.findAll() 처럼 쿼리를 1번만 날리라고 짰는데,
막상 뚜껑을 열어보니 JPA가 뒤에서 자기 맘대로 N번의 쿼리를 더 날려서 서버를 느려지게 만드는 현상.
해결방법
-- 1. QueryDSL에서의 fetchJoin()
public List<FreeBoard> findBoardsWithReplies() {
return queryFactory
.selectFrom(freeBoard)
.leftJoin(freeBoard.repliesList, reply).fetchJoin() // 여기에 fetchJoin() 추가!
.fetch();
}
--2. @EntityGraph
Spring Data JPA가 제공하는 기능으로, 쿼리문(@Query)에 직접 join fetch를 적는 대신 어노테이션만으로 페치 조인과 똑같은 효과를 낼 수 있습니다.
// repliesList를 한 번에(Fetch) 가져오라고 명시합니다.
@EntityGraph(attributePaths = {"repliesList"})
@Query("select f from FreeBoard f")
List<FreeBoard> join00();
--3. default_batch_fetch_size
실무에서 가장 중요하게 생각하는 설정 중 하나입니다. 게시판에서 '댓글'과 같은 @OneToMany(일대다) 관계를 fetch join으로 가져오면서
동시에 페이징(Pageable, limit/offset)을 하려고 하면, JPA는 경고를 뿜어내며 모든 데이터를 메모리에 끌고 와서 페이징을 해버립니다. (치명적인 메모리 장애 원인)
이럴 때 fetch join을 포기하고 Batch Size를 설정합니다.
동작 방식: N번의 추가 쿼리를 날리는 대신, 지정한 사이즈만큼 모아서 SQL의 IN 절을 사용해 1번의 쿼리로 뭉텅이로 가져옵니다.
예) YAML
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 1000 # 100 ~ 1000 사이의 값을 주로 사용합니다.
이렇게만 설정해 두면, 일반 Join 을 쓰거나 지연 로딩을 하더라도 N번의 쿼리가 1번(또는 아주 적은 수)의 IN 쿼리로 획기적으로 줄어듭니다.
3. countquery
페이징을 하는경우 fetch join을 쓰면 페이징 까지 조인이되서 많은 양의 데이타가 메모리에 올라온다 이 문제를 해결하기 위해 countquery를 쓴다.
예)
@Query(value = "select distinct f from FreeBoard f left join fetch f.repliesList",
countQuery = "select count(distinct f.bno) from FreeBoard f left join f.repliesList" )
Page<FreeBoard> join05(Pageable page);
댓글
댓글이 없습니다.
