코딩/sparta TIL

TIL 49 : 아~ 쉬고 싶다.

americanoallday 2025. 5. 8. 14:20

 

✅ 응답할 때 DTO로 반환하는 이유

  1. 원하는 정보만 응답하기 위해
    → Entity에는 DB 컬럼 전체가 담기지만, DTO는 클라이언트에 필요한 필드만 담는다.
  2. 무한 순환 참조 방지 (StackOverflow)
    → 양방향 연관관계에서 직렬화할 때 순환 참조 문제가 생길 수 있음.
  3. API 명세 분리 및 보안
    → 내부 엔티티 구조가 외부에 노출되지 않게 하기 위해.
  4. 쿼리 성능 최적화
    → 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;
}
  • MemberOrder를 알고 (orders)
  • OrderMember를 알아 (member)
    서로가 서로를 참조하고 있는 구조

 

🔸 직렬화 (Serialization)

  • 자바 객체를 JSON, XML 등으로 변환하는 과정
  • 예: Controller에서 return member → JSON으로 직렬화됨

 

🔸 순환 참조란?

  • 직렬화할 때, Member → Order → 다시 Member → 다시 Order … 무한 반복됨
  • 그 결과 → StackOverflowError 발생