본문 바로가기
Spring/- [시리즈] Spring 프로젝트 되살리기

Spring 프로젝트 되살리기 5 - 코드 리팩토링 3 (@EntityGraph 적용)

by 정구정구 2026. 7. 25.

이전 이야기

https://record47584.tistory.com/89

 

Spring 프로젝트 되살리기 4 - 코드 리펙토링 2 (@Valid 적용)

이전 이야기https://record47584.tistory.com/88 Spring 프로젝트 되살리기 3 - 코드 리펙토링 1 (중첩 if문 제거 등)이전 이야기https://record47584.tistory.com/87 Spring 프로젝트 되살리기 2 - ArgumentResolver 이슈이전 이

record47584.tistory.com

 

 

시나리오 5

이제 코드 리팩토링은 어느 정도 끝난 것 처럼 보인다.

그럼 지난번에 문제였던 N+1 처리가 똑바로 되어있는지 체크해보자.

(참고 : https://record47584.tistory.com/73)

 

N+1 문제의 개념과 해결 방법

N+1 문제란? 처음 조회를 위해 1번의 SQL이 실행되고, 연관된 데이터를 조회하기 위해 N번의 SQL이 추가로 실행되는 현상 지연 로딩(Lazy Loading) 된 연관 엔티티를 반복적으로 조회할 때 발생처음에는

record47584.tistory.com

 

 

리팩토링

    @Transactional(readOnly = true)
    public Page<TodoResponse> getTodos(int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);

        Page<Todo> todos = todoRepository.findAllByOrderByModifiedAtDesc(pageable);

        return todos.map(todo -> new TodoResponse(
                todo.getId(),
                todo.getTitle(),
                todo.getContents(),
                todo.getWeather(),
                new UserResponse(todo.getUser().getId(), todo.getUser().getEmail()),
                todo.getCreatedAt(),
                todo.getModifiedAt()
        ));
    }

 

N+1이 발생할 가능성이 있는 service 코드이다. 

Hibernate: 
    select
        t1_0.id,
        t1_0.contents,
        t1_0.created_at,
        t1_0.modified_at,
        t1_0.title,
        t1_0.user_id,
        u1_0.id,
        u1_0.created_at,
        u1_0.email,
        u1_0.modified_at,
        u1_0.password,
        u1_0.user_role,
        t1_0.weather 
    from
        todos t1_0 
    left join
        users u1_0 
            on u1_0.id=t1_0.user_id 
    order by
        t1_0.modified_at desc 
    limit
        ?

 

디버그 콘솔을 확인해보니 query를 N+1번 호출하지 않고 한 번으로 처리가 잘 되어 있다. 

    @Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC")
    Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);

 

repository 코드를 확인해보니까 JPQL로 처리가 되어있다. 기능적으로는 아무 문제 없다. 

    @EntityGraph(attributePaths = "user")
    Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);

 

그래도 위와 같이 @EntityGraph를 이용한다면 코드를 조금 더 간결하게(가독성 좋게) 만들 수 있다. 

 

주의! - @EntityGraph로 바꾸지 않는 편이 나은 경우

  • 여러 연관관계를 동시에 fetch 하면서 복잡한 WHERE 조건을 걸어야 하는 경우
  • DISTINCT가 필요한 컬렉션 fetch join의 경우