코딩/sparta TIL
TIL 49 : 아~ 쉬고 싶다.
americanoallday
2025. 5. 8. 14:20
✅ 응답할 때 DTO로 반환하는 이유
- 원하는 정보만 응답하기 위해
→ Entity에는 DB 컬럼 전체가 담기지만, DTO는 클라이언트에 필요한 필드만 담는다. - 무한 순환 참조 방지 (StackOverflow)
→ 양방향 연관관계에서 직렬화할 때 순환 참조 문제가 생길 수 있음. - API 명세 분리 및 보안
→ 내부 엔티티 구조가 외부에 노출되지 않게 하기 위해. - 쿼리 성능 최적화
→ JPQL에서 new DTO(...) 구문을 사용하면 필요한 필드만 select 가능.
✅ fetch join과 EAGER의 차이점
항목 |
fetch join | EAGER |
정의 | JPQL에서 명시적으로 연관 객체를 조인해서 한번에 조회 | 엔티티 연관 필드를 즉시 로딩하도록 설정 |
제어 권한 | 쿼리 수준에서 직접 제어 가능 | 엔티티 레벨에서 자동 작동 (제어 어려움) |
페이징 | 컬렉션 페치 조인은 페이징 불가능 | 사용 시 쿼리량 폭발 위험 |
추천 여부 | ✔ 실무에서 적극 활용 | ❌ 실무에서 지양 (특히 컬렉션) |
✅ fetch join과 EAGER, 뭐가 다름?
✅ 공통점:
- 둘 다 연관된 엔티티를 한번에 함께 조회
❌ 차이점:
항목 | fetch join | EAGER |
선언 위치 | JPQL 쿼리 내 | 엔티티 필드에 설정 |
제어 가능 여부 | ✔ 쿼리마다 다르게 사용 가능 | ❌ 항상 즉시 로딩됨 |
퍼포먼스 최적화 | 필요할 때만 | 쿼리 폭발 주의 |
페이징 시 | 주의 필요 (특히 컬렉션) | 제어 어려움 |
💡 핵심 정리:
fetch join은 선택적 사용 가능,
EAGER는 항상 즉시 조회 (컨트롤 안 됨 → 실무 비추천)
✅ N+1 문제 발생 원인
- LAZY 설정 + 연관 엔티티 접근 시점마다 쿼리 발생 → N+1
- 예: 게시글 10개 조회(N), 각 글의 댓글 1번씩 조회(+10)
💡 EAGER로 하면 N+1 안 생기냐?
- 컬렉션이면 생길 수 있음 (즉시 로딩이 쿼리를 바로 날리니까)
- 무조건 해결은 아님 → fetch join, EntityGraph 등으로 제어해야 함
✅ 3. EAGER로 하면 N+1 안 생기냐?
EAGER면 연관된 객체를 한번에 가져오긴 함. 근데 컬렉션(Collection) 인 경우는 다름
@Entity
class Member {
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders;
}
- 이 경우 Member 10명을 가져오면
→ 1(N) + 10 = N+1 발생
→ 각각 Order를 또 1번씩 추가 쿼리로 가져옴
💡 즉, EAGER는 쿼리를 제어하지 못하므로 N+1의 원인이 될 수 있음
✅ fetch join vs 일반 join 차이점
- join: 단순 SQL 조인 (엔티티까지 로딩 안 함)
- fetch join: 조인 + 해당 엔티티도 영속성 컨텍스트에 즉시 로딩
// 단순 join
select m from Member m join m.team t
// fetch join
select m from Member m join fetch m.team
일반 join은 영속성 컨텍스트에 안 올라간다고?
- 여기서 team은 조인했지만 조회되지 않음 (proxy 상태)
- m.getTeam().getName() 호출 시 → 또 쿼리 나감 (LAZY면)
👉 fetch join만 영속성 컨텍스트에 같이 올림
✅ @EntityGraph은 무슨 조인?
- 내부적으로 fetch join과 유사한 동작을 함
- JPQL 없이 Repository 인터페이스 메서드에서 연관객체 로딩 가능하게 해줌
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
✅ 5. EntityGraph는 fetch join의 어노테이션 버전?
정확히 말하면 기능은 유사하지만 방식이 다름.
- 내부적으로 fetch join처럼 동작
- @Query 없이 JPA 메서드 정의만으로 fetch join 효과
📌 사용 위치:
- JpaRepository 메서드에만 붙일 수 있음
- 복잡한 조건 쿼리에는 fetch join이 더 유연함
✅ fetch join에 DISTINCT 사용하는 이유
- 컬렉션 페치 조인 시 중복 엔티티 발생
- DISTINCT 붙이면 중복 제거됨 (DB + JPA 메모리 차원 둘 다 적용됨)
select distinct m from Member m join fetch m.orders
✅ @BatchSize 사용 방법
@OneToMany(mappedBy = "member")
@BatchSize(size = 100)
private List<Order> orders;
- 여러 건을 한 번에 IN 쿼리로 조회함
- hibernate.default_batch_fetch_size 로 전역 설정도 가능
✅ fetch join vs @EntityGraph vs @BatchSize
항목 |
사용 시점 | 장점 | 단점 |
fetch join | JPQL 쿼리에서 | 즉시 조인, 빠름 | 컬렉션은 페이징 불가 |
@EntityGraph | Repository 메서드에서 | 선언형, 간단함 | 복잡한 조건엔 부적합 |
@BatchSize | LAZY일 때 | N+1 완화 | N만큼 쿼리는 발생함 (IN절 묶음) |
✅ 변경된 필드만 update: @DynamicUpdate vs build.gradle
@DynamicUpdate는 JPA 수준에서의 SQL 컬럼 최적화이고,
build.gradle 설정은 JPA가 변경 감지할 때 로그를 자세히 보여주거나, 트래킹 관련 설정을 도와줌.
1. @DynamicUpdate
- 변경된 필드만 SQL에 포함 (update set 컬럼1=?, 컬럼2=?)
- Hibernate가 변경 감지 후, 필요한 필드만 업데이트
- 엔티티 클래스 위에 선언
@DynamicUpdate
@Entity
2. build.gradle 설정
- 변경 감지를 최적화하기 위한 기능을 설정할 수 있음
- 예: dirty checking 관련 로깅, 인라인 설정
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
tasks.withType(JavaCompile) {
options.compilerArgs += ["-parameters"]
}
bootRun {
jvmArgs = [
"-Dspring.jpa.properties.hibernate.show_sql=true",
"-Dspring.jpa.properties.hibernate.format_sql=true"
]
}
👉 정리: 실무에선 @DynamicUpdate가 간단하고 직관적인 해결책임
✅ 1. 양방향 연관관계, 직렬화, 순환 참조란?
// 부모
class Member {
@OneToMany(mappedBy = "member")
List<Order> orders;
}
// 자식
class Order {
@ManyToOne
Member member;
}
- Member는 Order를 알고 (orders)
- Order도 Member를 알아 (member)
→ 서로가 서로를 참조하고 있는 구조
🔸 직렬화 (Serialization)
- 자바 객체를 JSON, XML 등으로 변환하는 과정
- 예: Controller에서 return member → JSON으로 직렬화됨
🔸 순환 참조란?
- 직렬화할 때, Member → Order → 다시 Member → 다시 Order … 무한 반복됨
- 그 결과 → StackOverflowError 발생