Spring Boot 개념 정리(JPQL, N+1, )

제이 용·4일 전

JPQL

  • JPQL (Java Persistence Query Language)은 Entity 객체를 대상으로 쿼리를 작성하는 객체 지향 SQL이다.

SQL, JPQL의 차이

  • 사용하는 이유
    • 객체 중심 개발에 자연스럽게 녹아들 수 있음
      • JPQL은 SQL이 아닌, 객체(Entity) 를 대상으로 작성함
      • 테이블/컬럼이 아닌 엔티티/필드 기준으로 쿼리를 짜서 객체지향적인 코드를 유지 가능
  • 데이터베이스 독립성 확보
    • JPQL은 JPA 구현체(Hibernate 등)가 SQL로 변환해주므로 DBMS에 종속되지 않음
  • 정적 타입 지원 + 자동 바인딩
    • @Query, 메서드 네이밍, QueryDSL 등과 조합하면 타입 안정성 확보 및 IDE 자동완성 활용 가능
  • 복잡한 쿼리도 객체 기준으로 구성 가능
    • 복잡한 JOIN, 조건 쿼리도 객체 모델에 맞춰 작성 가능

JPQL vs JPA 차이점

JPQL쓰는 시점

사용 예시

  • SELECT
SELECT u FROM User u;      // 전체 유저 객체 조회

SELECT u.username FROM User u;  // 특정 필드만 조회
  • WHERE
SELECT u FROM User u WHERE u.age > 20;
  • ORDER BY
SELECT u FROM User u ORDER BY u.age DESC;
  • JOIN
SELECT * FROM orders o JOIN user u ON o.user_id = u.id WHERE u.username = 'KIM';

EAGER vs LAZY 로딩

요약본

N+1이란?

  • 엔티티 1개를 조회했더니, 추가 쿼리가 N개 더 실행되는 현상
  • 특히 연관된 데이터를 루프를 돌며 접근할 때 자주 발생

해결방법

Fetch Join

  • 연관된 엔티티를 한 번의 쿼리로 함께 조회하기 위한 JPQL 키워드
@Query("SELECT p FROM Post p JOIN FETCH p.comments WHERE p.user.username = :username")

List<Post> findAllWithCommentsByUsername(@Param("username") String username);
  • JOIN FETCH 키워드를 사용하면, 지연 로딩 설정이 되어 있더라도 즉시 로딩됨
  • 연관된 엔티티들을 SQL의 JOIN으로 함께 가져오기 때문에 추가 쿼리 발생이 없다.

주의사항

  • 컬렉션 페이징 불가

    • JOIN FETCH를 사용하면 중복된 결과로 인해 Pageable이 정상 동작하지 않음
  • 여러 컬렉션 Fetch Join 금지

    • JPA는 1개 이상의 컬렉션 Fetch Join을 허용하지 않음
  • 중복 제거 필요

    • DISTINCT를 사용하여 중복된 엔티티 제거 필요

BatchSize

  • Hibernate 설정을 통해 컬렉션 또는 연관된 엔티티들을 배치로 로딩할 수 있게 해주는 기능입니다.

    • JPA에서는 LAZY 로딩 전략이 기본이기 때문에 연관된 엔티티들을 개별 쿼리로 가져오며,
      이로 인해 N+1 문제가 발생할 수 있습니다.

    • BatchSize는 이러한 상황에서 Hibernate가 여러 엔티티를 한 번에 조회할 수 있도록 돕는 설정입니다.

    • 즉, LAZY 전략을 유지하면서도 IN 절을 활용한 일괄 조회가 가능해집니다.

글로벌 설정법

  • 글로벌 설정 (application.yml)
    spring:
    		jpa:
    			properties:
    				hibernate:
    					default_batch_fetch_size: 100
  • 개별 설정 (엔티티 또는 컬렉션 필드)
        @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) // ← 지연 로딩!
       @BatchSize(size = 100) 
       private List<Post> posts = new ArrayList<>();

주의 사항

  • BatchSize 크기

    • 너무 작으면 효과 미미, 너무 크면 IN 쿼리가 길어져 성능 저하 가능
  • Fetch Join과 병행 사용

    • 컬렉션 Fetch Join과 함께 쓰면 안 됨 (예: 페이징 깨짐)
  • LAZY 유지 조건

    • 이 설정은 LAZY인 경우에만 유효함

EntityGraph

  • Spring Data JPA에서 지원하는 기능으로, 연관관계를 JPQL 없이 즉시 로딩하도록 지정할 수 있다.
@EntityGraph(attributePaths = {"posts"})
@Query("SELECT u FROM User u")
List<User> findAllWithPosts();

DTO Projection

  • DB에서 원하는 컬럼만 선택하여 DTO로 직접 매핑하는 방식
@Query("SELECT new com.example.dto.UserDto(u.username, u.age) FROM User u")
List<UserDto> findAllUserDto();

마무리

  • 앞으로 N+1 문제에 대해서도 알아봤고, 해결방안까지 공부를 하였으니 앞으로의 문제에 어떤 방식이 더 효율적인 방식인지 판단할 수 있는 능력을 기를 수 있도록 해야겠다.

2개의 댓글

comment-user-thumbnail
3일 전

N+1 문제 발생 시 찾아가겠습니다

1개의 답글