Spring Boot

JPA Query DSL 및 N+1 문제 Troubleshooting

sounglikane 2024. 11. 20. 11:53

Project's Github link: https://github.com/gbognon25/spring-plus

 

GitHub - gbognon25/spring-plus: spring-plus

spring-plus. Contribute to gbognon25/spring-plus development by creating an account on GitHub.

github.com

 

배경(Background)

Schedule('Todo') 관리 애플리케이션의 데이터 조회 성능을 개선하기 위해 코드 리팩토링을 진행하던 중, JPQL로 작성된 'findByIdWithUser' 메서드에서 N+1 문제가 발생할 가능성을 발견했습니다. 이 문제는 애플리케이션의 성능 저하로 이어질 수 있었습니다.

Project에서 구현한 'getTodo()' method는 다음과 같습니다 ('TodoService' class에 있습니다):

 

'TodoRepository' Interface는 다음과 같습니다:

 

발단(Onset)

JPQL 기반의 구현에서 다수의 쿼리가 실행되어 성능이 저하될 위험을 인지했습니다. 특히, Todo entity와 연관된 User entity를 효율적으로 조회하지 못하는 구조가 N+1 문제를 유발할 가능성이 있었습니다.

 

전개(Development)

해결을 위해 TodoRepository에 QueryDSL 기반의 사용자 정의 메서드를 구현하기로 결정했습니다. 먼저 TodoRepository에 사용자 정의 인터페이스와 구현체를 추가하여 QueryDSL로 전환할 준비를 했습니다.

 

위기(Crisis)

QueryDSL을 적용하는 도중, 복잡한 연관 관계를 처리하면서 발생할 수 있는 추가적인 문제(예: 쿼리 최적화 부족 또는 예외 처리 누락)를 발견했습니다. 또한, 기존 JPQL과 다른 QueryDSL의 문법을 적응하고 적용하는 데 시간이 필요했습니다.

 

절정(Climax)

근본적인 문제를 해결하기 위해 다음과 같은 접근 방식을 채택했습니다:

1- Query DSL 설정

IDE는 IntelliJ입니다.

 

   1.1- 'build.gradle' file에 Query DSL dependencies 추가:

 

   1.2- 'Q Class' 자동으로 생성하기 위해 IntelliJ IDE 설정:

 

 

2- 새로운 Interface 생성

'TodoRepositoryCustom'이라는 interface를 만들어 'findByIdWithUser' 메서드를 정의했습니다.

 

3- QueryDSL 구현

'TodoRepositoryCustomImpl' class에서 QueryDSL의 'fetchJoin'을 활용해 'findByIdWithUser' 메서드를 구현했습니다.

 

'TodoRepository' interface에 적용:

 

Hibernate SQL 로그를 확인하여 fetchJoin이 올바르게 작동하고, 단일 쿼리로 데이터가 조회되는 것을 검증했습니다.

 

결말(Resolution)

QueryDSL로 리팩토링한 결과, N+1 문제를 근본적으로 해결할 수 있었으며, 단일 query로 'Todo'와 연관된 'User' 데이터를 효율적으로 조회하게 되었습니다. 이로써 코드 가독성과 유지보수성을 향상시켰으며, 추후 복잡한 조회 로직 확장에도 용이한 구조를 구축했습니다.

 

Tips

 

  • 연관된 엔티티를 조회할 때 QueryDSL의 fetchJoin을 항상 사용하여 N+1 문제를 방지하세요.
  • 개발 중에는 로그를 통해 query 성능을 모니터링하세요.