[Spring] fetch join (2)

이연우·2025년 8월 20일

TIL

목록 보기
99/100

📦 @BatchSize

  • 지연 로딩 시 연관 엔티티를 여러 개씩 묶어(IN 쿼리) 한 번에 조회하도록 하는 설정
  • “엔티티 1개 접근마다 1쿼리” → “여러 키를 IN으로 묶은 1쿼리”로 N+1 완화

💡 왜 사용할까?

  • 컬렉션 fetch join페이징 불가(DB 레벨 반영 X → 메모리 페이징) → OOM 위험 ⚠️
  • @BatchSize지연 로딩을 유지하면서도
    조회를 덩어리로(배치) 가져와 쿼리 수/비용 절감

🛠️ 어떻게 사용할까?

  • 필드에 선언(우선순위 높음)
@BatchSize(size = 100)
@OneToMany(mappedBy = "company")
private List<Tutor> tutorList;
  • 동작: 지연 로딩 발생 시, 현재 영속성 컨텍스트에 로드된 여러 부모의 자식 키를 모아
    where tutor.company_id in (?, ?, ...) 형태로 한 방에 가져옴

📎 예시 코드 및 실행 흐름

String query = "select c from Company c";

List<Company> companyList = em.createQuery(query, Company.class)
        .setFirstResult(0)
        .setMaxResults(2)
        .getResultList();

System.out.println("companyList.size() = " + companyList.size());

for (Company company : companyList) {
    System.out.println("company.getName() = " + company.getName());
    
    for (Tutor tutor : company.getTutorList()) {
        System.out.println("tutor.getName(): " + tutor.getName());
    }
}
@Entity
@Table(name = "company")
public class Company {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @BatchSize(size = 100)
    @OneToMany(mappedBy = "company")
    private List<Tutor> tutorList = new ArrayList<>();

    public Company() {
    }

    public Company(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public List<Tutor> getTutorList() {
        return tutorList;
    }

}
  1. select c from Company c + 페이징(예: 0~1) → sparta, etc 두 건만 우선 로드

  2. 각 회사의 tutorList 접근 시

    • BatchSize 미설정: Company마다 별도 쿼리 실행 (N+1)
    • BatchSize 설정: 한 번의 IN 쿼리로 두 CompanyTutor동시에 로드

🧭 fetch join vs @BatchSize

항목fetch join@BatchSize
목적즉시 함께 로드(한 번의 SQL)지연 시 여러 건 묶어 로드(IN)
적용 대상N:1, 1:1, (1:N은 중복·페이징 제약)주로 컬렉션(1:N), 엔티티 참조에도 적용 가능
페이징1:N 컬렉션에선 불가(메모리 페이징)가능(지연 로딩 유지)
중복/결과조인으로 중복 행 가능, DISTINCT 필요중복 없음(컬렉션 지연 로딩 시 개별 조회)
장점N+1 즉시 해소, 조회 한 번에 그래프 완성쿼리 수 감소, OOM 방지에 유리
권장 사용N:1 조회 최적화1\:N + 페이징 시 대안

🧠 요약 정리

@BatchSize 핵심

  • N+1 완화: 지연 로딩 시 IN 쿼리로 묶어서 로드
  • 필드 선언 > 글로벌 설정(우선순위 높음)
  • 글로벌 설정: 설정 파일에서 일괄 적용 가능 (아래 주의 참고)

📝 주의(이름 혼동 포인트)

  • 배치 조회의 전역 설정은 보통 hibernate.default_batch_fetch_size를 사용함
  • hibernate.jdbc.batch_size
    INSERT/UPDATE 같은 DML 배치 설정에 쓰이는 이름이라 혼동 주의해야 함

🔚 fetch join 총정리

  • 개념: 연관 엔티티/컬렉션을 한 쿼리로 함께 로드

    • 지연 로딩 설정과 무관하게 fetch join이 우선
  • 효과: N+1 해결 가능

  • 주의: 중복 발생 시 DISTINCT (Hibernate 6+ 일부 자동)

  • 1:N 컬렉션 + 페이징: DB 페이징 불가 → 메모리 페이징(OOM 위험)

    • 대안: 컬렉션은 fetch join 지양 + @BatchSize로 지연 로딩 최적화
  • 복잡 조회: 일반 JOIN으로 필요한 필드만 뽑아 DTO 반환(JPQL/QueryDSL)

0개의 댓글