[Spring] Spring Data JPA와 JPQL

이연우·2025년 8월 20일

TIL

목록 보기
100/100

⚙️ Spring Data JPA에서 JPQL 적용하기

🧠 왜 JPQL인가?

  • 🔎 Query Method만으로 한계
    • 복잡 조건·집계·조인 기반 조회는 메서드 이름만으로 한계가 있음
  • 🐢 N+1 발생
    • 연관 엔티티 접근 시 추가 쿼리 다수 실행 가능
  • 💡 해결
    • @Query 로 JPQL 직접 작성, @Param 으로 파라미터 바인딩
@Query("select new ...Dto(s.name, s.age) from Student s " +
       "where s.name like %:keyword% and s.age between :minAge and :maxAge")
List<StudentDto> findByNameContainingAndAgeBetween(
    @Param("keyword") String keyword,
    @Param("minAge") int minAge,
    @Param("maxAge") int maxAge);

🧾 설정 파일 (properties vs yml)

  • application.properties

    • 간단한 key=value
    • ⚠️ 동일 접두사 키가 많으면 중복/가독성 저하
  • application.yml

    • 계층적 구조가독성 ↑, 중복 ↓
    • 예: JPA 로그 옵션/방언/DDL 전략을 한 눈에 정리 가능

💡 둘 다 함께 둘 수 있지만 properties 우선순위가 더 높음
     보통 하나로 통일해서 사용


🧱 도메인 & DTO

🧩 엔티티

  • Tutor (1) ↔ (N) Student
    • Tutor: @OneToMany(mappedBy="tutor") List<Student> students
    • Student: @ManyToOne(fetch=LAZY) @JoinColumn(name="tutor_id") Tutor tutor

📦 DTO

  • TutorStudentCountDto(name, count) : 튜터별 학생 수 집계 결과
  • StudentDto(name, age) : 검색 결과 슬림 뷰
  • StudentTutorDto(name, age, tutorName) : 학생+튜터명 혼합 응답

🧰 Repository JPQL 패턴

1. 🧮 집계 + 그룹바이 → DTO 프로젝션

@Query("select new ...TutorStudentCountDto(t.name, count(s)) " +
       "from Tutor t join t.students s group by t.name")
List<TutorStudentCountDto> findTutorStudentCounts();

2. 🔍 조건 검색(이름 like, 나이 범위) → DTO 프로젝션

@Query("select new ...StudentDto(s.name, s.age) " +
       "from Student s where s.name like %:keyword% " +
       "and s.age >= :minAge and s.age <= :maxAge")
List<StudentDto> findByNameContainingAndAgeBetween(...);

3. ⚡ N+1 완화 F fetch join

@Query("select s from Student s join fetch s.tutor")
List<Student> findStudentsWithTutor();
  • 학생과 연관된 튜터를 한 번의 쿼리로 로드 → 연관 접근 시 추가 쿼리 방지

🧭 Service & Controller 흐름

  • Service: Repository 호출만 담당(비즈니스 규칙·매핑)

    • getTutorStudentCounts() → 집계 DTO 반환
    • searchStudents() → 조건 검색 DTO 반환
    • getStudentsWithTutors() → 엔티티 조회 후 StudentTutorDto로 매핑
  • Controller: REST 엔드포인트

    • GET /tutors/student-counts → 튜터별 학생 수
    • GET /students/search?keyword=&minAge=&maxAge= → 학생 검색
    • GET /students/with-tutors → 학생 + 튜터 이름(페치 조인 기반)

🧪 실행 결과 요약

  • 튜터별 학생 수: group by + DTO 프로젝션 → 집계 SQL 실행 확인
  • 학생 조건 검색: like + age 범위 → DTO로 바로 매핑
  • 학생-튜터 조회: join fetch s.tutor → N+1 없이 한 방 조회

📸 Postman & 콘솔 SQL 캡처로 JPQL → SQL 변환이 잘 반영되는 것을 확인


🚀 @PostConstruct 활용

  • application.yml
profiles:
  active: dev
  • @Component @Profile("dev") + @PostConstruct
    • 앱 기동 시 개발 프로필에서만 테스트 데이터 넣기
    • 튜터 3명, 학생 30명 생성 → 학생을 3명의 튜터에 순차 할당

개발 환경 데이터 자동화로 API 시연/테스트가 한결 편해짐


🧠 요약 정리

구분핵심 내용포인트
JPQL 필요성Query Method 한계, N+1 발생@Query + @Param 활용
프로젝션DTO 직접 생성자 프로젝션트래픽 절감 · 표현 최소화
집계group by + countTutorStudentCountDto
조건 검색 like + 범위(>=, <=)StudentDto
N+1 완화join fetchfindStudentsWithTutor()
설정properties vs ymlyml 가독성↑, properties 우선순위↑
부트스트랩@PostConstruct + @Profile("dev")개발 프로필에서만 시드 데이터

0개의 댓글