Back-End/Spring (SpartaCC)

[Spring] JPA - JPQL, QueryDSL, N+1 문제

남건욱 2023. 7. 27. 19:48
반응형
JPQL

- Table 이 아닌 Entity(객체) 기준으로 작성하는 쿼리를 JPQL이라고 하며 이를 사용할 수 있도록 EntityManger 또는 @Query 구현체를 통해 JPQL 쿼리를 사용할 수 있다.

 

JQL : Entity 명으로 쿼리짤때 쓰이는 언어 (쓰이는 곳. JPQL, QueryDSL)

SQL : Table 명으로 쿼리짤때 쓰이는 언어 (쓰이는 곳. JDBC, SQL Mapper)

 

 

 

EntityManager.createQuery()

- 쿼리 문자열과 Entity 를 직접 넣어서 쿼리를 작성한다.

- setParameter 와 같이 key, value 문자열을 통해서 쿼리 파라미터를 매핑할 수 있다.

 

 

 

코드에 문자열이 들어가는게 안 좋은 이유

- 문자열은 오타가 발생할 여지가 많다.

- 개발할때 같은 공통적인 문자열이 있을 때 한 군데에서 수정이 일어나면 모두 수정해야 한다.

- 잘못된 코드가 있더라도 문자열 자체를 컴파일러가 검사하지는 않기 때문에 컴파일 시점에 잡지 못한다.

- 이로 인해 버그가 있더라도 메서드를 실행하는 시점인 런타임시점에 버그가 발생한다.

- 런타임 시점에 발생한 버그는 서비스 정합성에 영향을 주며 원인을 찾기도 어렵다.

 

해결방법

- 문자열을 포함하여 구현된 기능들은 객체화 또는 함수화 해서 컴파일 시 체크되도록 한다.

- 문자열로 선언된 변수들은 상수로 선언하여 공통적으로 관리한다. (상수 클래스를 사용하면 좋다)

 

 

 

@Query (repository interface)

- @Query의 인자값으로 간단하게 쿼리작성이 가능하다.

- 쿼리를 작성할 때는 테이블명이 아닌 Entity명으로 조회한다.

 

 

 

 

QueryDSL (JPAQueryFactory)

- Entity의 매핑정보를 활용하여 쿼리에 적합하도록 쿼리 전용 클래스(Q클래스)로 재구성해주는 기술

- 여기에 JPAQueryFactory을 통한 Q클래스를 활용할 수 있는 기능들을 제공한다.

 

 

 

@DynamicInsert

- 이 어노테이션을 엔티티에 적용하게 되면 Insert 쿼리를 사용할 때 null 인 값은 제외하고 쿼리문이 만들어진다.

 

 

 

@DynamicUpdate

- 이 어노테이션을 엔티티에 적용하게 되면 Update 쿼리를 사용할 때 null인 값은 제외하고 쿼리문이 만들어진다.

 

 

 

 

Projection의 기능

- 원하는 필드만 지정해서 조회 가능

- 여러 필드 합쳐서 재정의 필드(Alias) 조회 가능 (Nested 프로젝션)

- 조회 필드 그룹을 인터페이스 또는 클래스로 만들어놓고 재사용이 가능하다.

 

 

 

Projection 필드 사용방법

1. get필드() 메서드로 정의

- 정의한 필드만 조회하기 때문에 Closed 프로젝션이라고 한다.

- 쿼리를 줄이므로 최적화할 수 있다.

- 메서드 형태이기 때문에 Java 8의 메서드를 사용해서 연산을 할 수 있다.

 

2. @Value로 정의

- 전체 필드를 조회할 수밖에 없어서 Open 프로젝션이라고 한다.

- @Value(SpEL)을 사용해서 연산을 할 수 있다.

- 스프링 빈 들의 메서드도 호출 가능하다.

- SpEL을 엔티티 대상으로 사용하기 때문에 쿼리 최적화를 할 수 없다. 

 

 

 

TestContainers

- 도커 환경에서 데이터베이스를 실행하여 테스트 환경을 쉽게 구축할 수 있게 해주는 라이브러리

- 개발 환경에 데이터베이스를 사용하지 않기 때문에 테스트 때문에 발생하는 더미 데이터를 줄일 수 있다.

- H2와 같은 인메모리 DB를 사용하는 것이 아니라서 실제 환경과 거의 비슷한 환경으로 데이터베이스를 테스트할 수 있다.

- 테스트가 느려지는 단점이 있다.

 

 

 

FixtureMonkey

- 네이버에서 만든 테스트 생성 객체를 자동으로 생성해 주는 자바 라이브러리

- Mock 객체를 보다 쉽게 생성하기 위해서 사용

 

 

 

테스트 정리

단위 테스트

- 각 계층(클래스) 별로 테스트 케이스 작성

 

통합 테스트

- 실행될 때마다 랜덤 하게 변경되는 시나리오를 만들고 그에 따른 데이터를 미리 생성(Docker 환경의 데이터베이스)

- 모든 엔드포인트에 대해서 테스트

- 사전에 데이터를 미리 만들어둔 것을 통해서, 결과를 예측하고 검증할 수 있음

- 단위테스트는 Pull Request에서 검증하는 용도(CI)

- 통합테스트는 정기 배포 당일 생성한 브랜치에 대해서 검증하고 검증이 완료된다면 자동으로 배포하는 프로세스(CD)

 

 

 

 

Propagation (전파 전략)

PROPAGATION_REQUIRED (JpaTranscationManager Default)

 

부모 트랜잭션이 존재할 경우

- 부모 트랜잭션에 참여한다.

 

부모 트랜잭션이 없을 경우

- 새 트랜잭션을 시작한다.

일반적으로 사용되는 트랜잭션의 전파 유형이다. 어떻게 해서든 Transcation을 시작하는 전파 유형

 

 

PROPAGATION_SUPPORTS

 

부모 트랜잭션이 존재할 경우

- 부모 트랜잭션에 참여한다.

 

부모 트랜잭션이 없을 경우

- non-transactional 하게 동작한다.

부모를 따라서 전파되는 유형

 

 

 

PROPAGATION_MANDATORY

 

부모 트랜잭션이 존재할 경우

- 부모 트랜잭션에 참여한다.

 

부모 트랜잭션이 없을 경우

-  Exception이 발생한다.

트랜잭션에 참여하도록 강제하는 유형

 

 

 

PROPAGATION_REQUIRES_NEW

부모 트랜잭션 유무에 상관없이

- 새 트랜잭션을 시작한다.

 

부모 트랜잭션이 존재할 경우

- 부모 트랜잭션을 중지시킨다.

무조건 새 트랜잭션을 생성하도록 강제하는 유형

 

 

 

PROPAGATION_NOT_SUPPORTED

부모 트랜잭션 유무에 상관없이

- non-transactional 하게 동작한다.

 

부모 트랜잭션이 존재할 경우

- 부모 트랜잭션을 중지시킨다.

 

 

 

PROPAGATION_NEVER

부모 트랜잭션이 존재할 경우 Exception이 발생한다.

항상 non-transcational 하게 동작하는 유형

 

N + 1 문제

- 엔티티 하나를 조회하기 위해서 1:N으로 연관된 엔티티까지 조회해서 쿼리문이 N+1번 날아가는 문제

 

이로 인해 아래와 같은 시스템에 심각한 성능 저하가 일어날 수 있다

/ Comment 조회 - 1번

/ Comment의 개수(각 Comment가 가지고 있는 Board 조회) - N번

이렇게 하면 N+1번의 쿼리가 발생하는 것이다

 

 

 

 

N + 1 문제 해결방법 3가지

- GlobalFetch

- Fetch Join

- EntityGraph

 

 

GlobalFetch

- 글로벌 패치 전략이란, 엔티티를 생성할 때(컴파일 시점) 결정되는 연관관계 전략이다

- 해결방법은 @ManyToOne 속성에 fetch 속성으로 LAZY를 주면 된다

 

 

 

Fetch Join

- 조인할 때 연관된 엔티티나 컬렉션을 함께 조회하려고 할 때 사용한다 결과는 EAGER와 똑같지만 과정은 다르다 EAGER의 경우에는 N+1 쿼리가 발생하지만 Fetch Join의 경우에는 한 번이 쿼리문으로 해결이 가능하다

 

- Spring Data JPA에서는 @Query 어노테이션을 이용하여 JPQL를 생성할 수 있다

 

- 사용하는 방법은 위와 동일하게 join fetch 뒤에 연관된 엔티티나 컬렉션을 적어주면 된다

 

 

 

EntityGraph (+ QueryDSL과 더불어 중요)

- @EntityGraph도 마찬가지로 EntityGraph 상에 있는 Entity들의 연관관계 속에서 필요한 엔티티와 컬렉션을 함께 조회하려고 할 때 사용한다.

- Spring Data JPA에서 적용하려는 메서드 위에 @EntityGraph 어노테이션을 달고 옵션을 준다

- attributePaths는 같이 조회할 연관 엔티티명을 적으면 된다.

- , (콤마)를 통하여 여러 개를 줄 수도 있다

반응형
프로필사진

남건욱's 공부기록