JPA N+1 문제 해결 및 트랜잭션 격리성 [SB 3기]

[SB 3기] 코드잇 스프린트 위클리페이퍼 9주차

카테고리

프로그래밍/소프트웨어 개발

서브카테고리

웹 개발

대상자

JPA(Java Persistence API) 및 트랜잭션 관리에 관심 있는 개발자

핵심 요약

  • N+1 문제 발생 원인: 연관 엔티티의 Lazy Loading 또는 Eager Loading으로 인한 추가 쿼리 발생
  • 해결 방안: @Query("SELECT m FROM Member m JOIN FETCH m.team") 또는 @EntityGraph 사용
  • 트랜잭션 격리성 문제: Dirty Read, Non-repeatable Read, Phantom Read 발생 시 READ COMMITTED ~ SERIALIZABLE 격리 수준 적용

섹션별 세부 요약

1. N+1 문제 발생 원인

  • 연관 엔티티 조회 시 추가 쿼리 발생:

- member.getTeam().getName() 호출 시 N개의 쿼리 실행

- 예: 100명의 회원 조회 시 101개의 쿼리 실행

  • Lazy/Eager Loading 한계:

- Lazy Loading 시 런타임에 쿼리 발생

- Eager Loading 시 컬렉션 반복 접근 시 N+1 문제 발생

2. N+1 문제 해결 방안

  • JPQL JOIN FETCH 사용:

```java

@Query("SELECT m FROM Member m JOIN FETCH m.team")

List findAllWithTeam();

```

- 장점: 직관적, 복잡 쿼리 지원

- 단점: 페이징 처리 불가, 중복 데이터 발생 시 DISTINCT 필요

  • @EntityGraph 활용:

```java

@EntityGraph(attributePaths = {"team"})

@Override

List findAll();

```

- 장점: 코드 간결, 페이징 처리 가능

- 단점: 복잡 조인/필터링 쿼리에 적합하지 않음

3. 트랜잭션 격리성 문제

  • 격리성 보장 실패 시 발생 문제:

- Dirty Read: 커밋되지 않은 데이터 읽기

- Non-repeatable Read: 동일 쿼리 결과 불일치

- Phantom Read: 동일 조건 쿼리 시 행 추가 발생

  • 예시 시나리오:

- 쇼핑몰 재고 동시 주문 시 격리성 미보장 시 재고 초과 판매 발생

4. 트랜잭션 격리 수준

| 격리 수준 | 설명 | 방지되는 문제 |

|------------------|-----------------------------------|----------------------------|

| READ UNCOMMITTED | 커밋되지 않은 데이터도 읽음 | ❌ Dirty Read 허용 |

| READ COMMITTED | 커밋된 데이터만 읽음 (기본값) | ✅ Dirty Read 방지 |

| REPEATABLE READ | 동일 쿼리 결과 일관성 보장 | ✅ Non-Repeatable Read 방지 |

| SERIALIZABLE | 트랜잭션 순차 실행 보장 (성능 저하) | ✅ 모든 문제 방지 |

결론

  • N+1 문제 해결: JOIN FETCH 또는 @EntityGraph 사용, 페이징 처리 필요 시 @EntityGraph 선택
  • 트랜잭션 격리 수준 선택:

- 성능 우선: READ COMMITTED

- 데이터 일관성 우선: REPEATABLE READ 또는 SERIALIZABLE

- 예: 재고 관리 시 REPEATABLE READ 적용하여 Phantom Read 방지