Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- JAVA객체지향
- Java데이터 타입
- 상수
- multi-threading
- Thread
- OPP개념
- 집합관계
- this예약어
- java변수
- 메서드 오버로딩
- 형 변환
- for문
- 시스템 환경 변수 편집
- IntelliJ IDEA
- 포함관계
- Java
- 인텔리제이 기초 설정
- While
- 컴파일
- 인텔리제이 한글 깨짐 해결법
- 반복문
- continue문
- JAVA기초
- break문
- 메서드
- function
- 연관관계
- 생성자
- 접근제어지시자
- 자바 멀티스레딩
Archives
- Today
- Total
최원종의 개발 블로그
V3-6 연관 관계 및 N + 1 문제 확인 본문
application-dev.yml 파일
더보기
server:
servlet:
encoding:
charset: utf-8
force: true
port: 8080
logging:
level:
root: INFO #모든 라이브러리는 INFO 이상만 출력
com.tenco: DEBUG # 내 프로젝트는 DEBUG 이상 모두 출력
spring:
mustache:
servlet:
# 머스태치 템플릿 엔진에서 request 객체와 세션 객체에 접근할 수 있도록 허용하는 설정 추가
expose-session-attributes: true
expose-request-attributes: true
#데이터베이스 연결 설정 (MySQL)
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/myblog?serverTimezone=Asia/Seoul
username: root
password: root
# driver-class-name: org.h2.Driver
# url: jdbc:h2:mem:test
# username: sa
# password:
h2:
console:
enabled: true
# 초기 데이터 설정
sql:
init:
data-locations:
- classpath:db/data.sql
jpa:
hibernate:
# create 애플리케이션 시작시 테이블 새로 생성
# 기존 데이터는 모드 삭제됨 (개발용)
ddl-auto: update
#SQL 쿼리를 콘솔에 출력 (개발용)
show-sql: true
properties:
hibernate:
# SQL 쿼리를 보기 좋게 포맷팅
format_sql: true
# N + 1 문제를 완화 하기위한 완충 장치 설정
# 기본 WHERE 절에서 in 절(in 쿼리로 변경 해준다)
default_batch_fetch_size: 10
# data.sql 파일을 하이버네티트 초기화 이후에 실행
# data.sql 파일을 Hibernate 초기화 이후에 실행
defer-datasource-initialization: true
application-dev.yml 파일 변경점
1. 데이터베이스 연결 전환 (H2 → MySQL)
변경 전: 가상의 메모리 DB인 H2를 사용 (서버 재시작 시 데이터 삭제)
변경 후: 실제 물리 DB인 MySQL에 연결함.
2. JPA ddl-auto 전략 변경 (create → update)
변경 전 (create): 서버 실행 시마다 기존 테이블을 삭제하고 새로 만듬. (데이터 휘발)
변경 후 (update): 기존 테이블과 엔티티 객체를 비교하여 변경분만 반영함.
기존 데이터는 유지되므로 실제 개발 단계에서 더 많이 사용됨.
3. 성능 최적화: default_batch_fetch_size 추가
default_batch_fetch_size: 10
사용 이유: JPA에서 연관된 엔티티를 조회할 때 쿼리가 너무 많이 나가는 N+1 문제를 완화.
작동 원리: 1개씩 10번 물어볼 쿼리를 IN 절을 사용하여 한 번에 10개씩 묶어서 가져옴.
효과: DB 네트워크 통신 횟수가 획기적으로 줄어들어 전체적인 조회 성능이 크게 향상됨.
연관관계 핵심 정리 사항
JPA에서 DB의 JOIN이 필요한 상황에서 엔티티의 연관관계를 설정해 데이터를 조회할 때,
즉시 로딩과 지연 로딩의 차이와 지연 로딩에서 발생할 수 있는 N+1 문제, 그리고 이를 해결하기 위한 방법 정리
개념 정리
JPA에서 데이터베이스의 JOIN이 필요한 상황에서 엔티티 간 연관관계를 설정하여데이터를 조회할 수 있습니다.
이때, 연관된 데이터를 가져오는 방식으로 즉시 로딩(Eager Loading)과 지연 로딩(Lazy Loading)을 설정할 수 있습니다.
1. 즉시로딩 (Eager Loading)
- 엔티티를 조회할 때 연관된 데이터도 즉시 함께 조회합니다.
- 예: @ManyToOne(fetch = FetchType.EAGER) 설정.
- 장점: 한 번의 쿼리로 필요한 모든 데이터를 가져옴.
- 단점: 불필요한 데이터까지 조회할 경우 성능 저하 가능.
2. 지연로딩 (Lazy Loading)
- 엔티티 조회 시 연관된 데이터는 실제로 필요할 때(접근 시) 조회됩니다.
- 예: @ManyToOne(fetch = FetchType.LAZY) 설정.
- 장점: 초기 쿼리 부담이 적고 필요한 데이터만 조회.
- 단점: 연관 데이터를 반복적으로 접근할 경우 **N+1 문제** 발생 가능.
3. N+1 문제 (EAGER 전략에 리스트 출력 시)
- 지연 로딩 설정 시, 메인 엔티티를 조회하는 1번의 쿼리와
연관된 엔티티를 조회하기 위해 N번의 추가 쿼리가 발생하는 문제.
- 예: 부모 엔티티 1개를 조회하고, 연관된 자식 엔티티 N개를 각각 조회하면 총 1+N번의 쿼리가 실행.
- 결과: 성능 저하.
4. Batch Fetch Size (N + 1 완화 작용)
- Batch Fetch Size는 application.yml에 한 줄만 추가하면 전역적으로 N+1 문제를 완화합니다.
- 지연 로딩은 유지하되, 지연 로딩이 발생할 때 하나씩 가져오지 않고 IN 쿼리로 묶어서 가져옵니다.
# application.yml
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100
Batch Fetch Size 동작 원리
-- Batch Fetch Size 없을 때 (N+1 문제)
SELECT * FROM board; -- 1번
SELECT * FROM user WHERE id = 1; -- 2번
SELECT * FROM user WHERE id = 2; -- 3번
SELECT * FROM user WHERE id = 3; -- 4번
... -- 10번까지 반복
-- 총 11번 쿼리 실행
-- Batch Fetch Size: 100 설정 시 (IN 쿼리로 변환)
SELECT * FROM board; -- 1번
SELECT * FROM user WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); -- 2번
-- 총 2번 쿼리 실행
Batch Fetch Size 요약
지연 로딩이 발생할 때, 개별 SELECT가 아닌 IN 절을 사용한 SELECT로 변환됨.
Batch Size가 100이고 데이터가 250개면, IN 쿼리가 3번(100 + 100 + 50) 실행됨.
완전한 1+1이 아니라 1 + N/size .
5. N+1 문제 해결 방법: JOIN FETCH(N+1정밀제어)
- JPQL에서 JOIN FETCH를 사용해 연관된 엔티티를 한 번의 쿼리로 함께 조회.
- 예: SELECT p FROM Parent p JOIN FETCH p.children
- 장점: 단일 쿼리로 모든 데이터를 가져와 N+1 문제를 방지.
- 주의: 복잡한 연관관계나 대량 데이터 조회 시 쿼리 성능 최적화 필요.
JOIN FETCH 구문에 이해
Board 코드
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@OneToMany(mappedBy = "board", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();
}
@Entity
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
private Blog blog;
}
Blog와 연관된 Comment를 한 번에 조회
// Blog와 연관된 Comment를 한 번에 조회
String jpql = "SELECT b FROM board b JOIN FETCH b.comments";
List<Blog> blogs = entityManager.createQuery(jpql, Blog.class).getResultList();'Spring boot 입문' 카테고리의 다른 글
| V3-8 게시글 삭제(인증 검사와 인가처리 ) (0) | 2026.05.13 |
|---|---|
| V3-7 게시글 쓰기(로그인한 사용자와 게시글 연결) (0) | 2026.05.13 |
| V3-5 로그인& 로그아웃(세션 기반 사용자 인증) (1) | 2026.05.12 |
| V3-4 회원가입(사용자 등록과 JPA 영속성 활용) (0) | 2026.05.12 |
| V3-3 게시글 목록보기(연산관계로 작성자 정보 표시) (0) | 2026.05.11 |
