1. 학습 주제
이번에는 JPQL 조회를 더 확장해서 조인, 페이징, 그룹 함수 이후 흐름으로
연관 엔티티를 함께 조회하는 JOIN,
JPQL로 표현하기 어려운 경우 직접 SQL을 사용하는 Native Query,
그리고 이후 JPA 사용을 더 단순하게 만들어주는 Spring Data JPA의 시작 구조까지 정리했다.
큰 흐름은 이렇게 이어진다.
- JPQL은 엔티티 중심 조회 언어다
- 엔티티 간 관계를 기반으로 JOIN도 할 수 있다
- 그래도 SQL이 더 적합한 경우에는 Native Query를 쓸 수 있다
- 그리고 이런 반복 작업을 더 줄이기 위해 Spring Data JPA를 쓰게 된다
즉 JPA를 배우는 흐름이
JPQL -> Native Query -> Spring Data JPA
순으로 자연스럽게 확장되는 구간이었다.
2. JPQL에서 JOIN이 필요한 이유
엔티티를 하나만 조회하는 경우는 비교적 단순하다.
하지만 실제 서비스에서는 메뉴만 필요한 게 아니라,
그 메뉴가 속한 카테고리 이름까지 함께 보고 싶은 경우가 많다.
데이터베이스에서는 이런 요구를 보통 JOIN으로 해결한다.
JPA에서도 마찬가지로 JOIN이 필요하지만, SQL과는 조금 다르게 쓴다.
중요한 점은 JPQL의 JOIN은:
- 테이블 이름이 아니라 엔티티를 기준으로 하고
- 외래키 컬럼명이 아니라 연관관계 필드를 기준으로 한다
는 것이다.
즉 SQL처럼 직접 FK 컬럼을 적는 게 아니라
엔티티가 이미 가지고 있는 연관관계를 따라간다고 이해하면 된다.
3. 내부 조인
내부 조인은 양쪽에 모두 값이 있는 경우만 가져오는 방식이다.
JPQL에서는 다음처럼 연관관계 필드를 따라 JOIN할 수 있다.
- 메뉴 엔티티에서 카테고리 엔티티로 JOIN
- 메뉴와 카테고리가 정상적으로 연결된 행만 조회
이때 중요한 감각은
“조인을 내가 컬럼 기준으로 쓰는 게 아니라, 객체 참조 기준으로 쓴다”는 점이다.
즉 SQL에서는:
- menu.category_code = category.category_code
를 직접 적는 감각이었다면,
JPQL에서는:
- 메뉴가 가진 category 참조를 따라간다
는 방식으로 생각하면 된다.
4. 외부 조인
외부 조인은 양쪽 데이터가 완전히 일치하지 않아도 한쪽 기준으로 결과를 유지하고 싶을 때 사용한다.
예를 들어:
- 카테고리는 있는데 메뉴가 없는 경우
- 또는 메뉴는 있는데 연결된 값이 없는 경우
를 포함해서 보고 싶다면 외부 조인이 필요하다.
즉 외부 조인은
매칭되지 않는 데이터도 버리지 않고 유지하는 조회 방식이다.
실무에서는 “전체 목록은 보여주되 연결된 값이 없으면 null로 보이게” 하고 싶을 때 자주 사용된다.
5. 컬렉션 조인
연관관계에서 단일 참조가 아니라 컬렉션을 JOIN하는 경우도 있다.
예를 들면:
- 카테고리 하나
- 그 카테고리에 속한 여러 메뉴들
처럼 OneToMany 관계에서
카테고리의 메뉴 목록을 기준으로 조인하는 경우다.
이때는:
- 하나의 카테고리에서
- 여러 메뉴가 펼쳐지는 결과
가 나오게 된다.
즉 컬렉션 조인은
하나의 부모 엔티티가 가진 여러 자식 엔티티를 조회하기 위한 JOIN이라고 보면 된다.
6. FETCH JOIN
이번 범위에서 특히 중요한 것이 Fetch Join이다.
Fetch Join은 단순히 JOIN해서 조건을 걸거나 값을 가져오는 것과 조금 다르다.
목적이 연관 객체를 한 번에 같이 로딩하는 것에 더 가깝다.
왜 필요하냐면, 지연 로딩이 걸려 있는 연관 객체를 나중에 하나씩 접근하면
추가 SQL이 계속 발생할 수 있기 때문이다.
즉 일반 JOIN은 “조회 조건/결과” 목적이라면,
Fetch Join은 “연관 엔티티를 함께 로딩”하는 목적이 강하다.
한 줄로 정리하면:
- 일반 JOIN: 조건과 결과를 위한 조인
- FETCH JOIN: 연관 객체를 즉시 함께 가져오기 위한 조인
이다.
7. Fetch Join을 쓰는 이유
Fetch Join의 핵심 목적은 성능 최적화와 연관 객체 로딩 방식 제어에 있다.
예를 들어 메뉴 목록을 조회한 뒤,
각 메뉴의 카테고리 이름을 하나씩 꺼내면
지연 로딩 구조에서는 추가 조회가 반복될 수 있다.
이럴 때 Fetch Join을 사용하면:
- 메뉴를 가져올 때
- 카테고리도 함께 조회해
- 나중에 추가 SQL 없이 사용할 수 있다
즉 Fetch Join은
연관 엔티티를 미리 당겨와서 조회 횟수를 줄이는 전략이라고 이해하면 된다.
8. Native Query란 무엇인가
JPA는 객체 중심 기술이지만, 모든 상황에서 JPQL만으로 충분한 것은 아니다.
복잡한 SQL, DB 전용 함수, 세밀한 튜닝이 필요한 경우에는 직접 SQL을 쓰는 것이 더 적절할 수 있다.
이럴 때 사용하는 것이 Native Query다.
Native Query
JPA 안에서 직접 SQL을 작성해서 실행하는 방식
즉 Native Query는:
- JPA를 쓰지만
- 조회 문법은 DB SQL 그대로 사용한다
는 뜻이다.
9. Native Query를 쓰는 이유
Native Query가 필요한 경우는 보통 이런 상황이다.
- JPQL로 쓰기 불편한 복잡한 쿼리
- DB 전용 함수 사용
- 서브쿼리, 통계, 튜닝이 필요한 쿼리
- SQL 결과를 더 직접적으로 다루고 싶은 경우
즉 JPA가 객체 중심이라 편하긴 하지만,
SQL이 더 잘 맞는 순간에는 Native Query가 자연스러운 선택이 된다.
따라서 Native Query는
JPA를 버리는 게 아니라, JPA 안에서 SQL을 직접 쓰는 예외 통로라고 보면 된다.
10. Native Query와 결과 매핑
Native Query는 결과를 어떤 형태로 받을지에 따라 느낌이 달라진다.
1. 엔티티로 매핑 가능한 경우
조회 결과가 엔티티의 모든 컬럼을 포함하면
결과를 엔티티 타입으로 받을 수 있다.
즉 SQL은 직접 쓰지만, 결과는 Menu 같은 엔티티 객체로 받을 수 있다.
2. 엔티티로 매핑하기 어려운 경우
일부 컬럼만 조회하거나 집계 결과처럼 구조가 다르면
보통 Object[] 형태로 받게 된다.
이 경우는:
- 첫 번째 칸은 이름
- 두 번째 칸은 가격
식으로 배열 인덱스로 접근하는 방식이 된다.
즉 Native Query도 결국
결과를 어떻게 해석할지가 중요하다.
11. 자동 결과 매핑
복잡한 Native Query에서는 엔티티 컬럼과 추가 계산 컬럼이 함께 나올 수 있다.
예를 들어:
- 카테고리 기본 정보
- 카테고리별 메뉴 개수
같이 한 결과셋에 섞여 있을 수 있다.
이럴 때는 @SqlResultSetMapping 같은 방식을 사용해서
결과를 어떤 엔티티와 어떤 추가 컬럼으로 해석할지 지정할 수 있다.
즉 자동 결과 매핑은
SQL 결과 구조를 JPA가 이해할 수 있는 형태로 설명해주는 작업이라고 보면 된다.
12. Named Native Query
Native Query를 코드 안에 매번 문자열로 직접 적을 수도 있지만,
반복적으로 쓰는 쿼리는 미리 이름을 붙여 등록해둘 수 있다.
이게 Named Native Query다.
즉:
- 쿼리를 엔티티 쪽에 미리 정의해두고
- 나중에 이름으로 꺼내 실행하는 방식
이다.
장점은 다음과 같다.
- 쿼리 재사용 가능
- 코드에서 문자열 SQL이 줄어듦
- 의미 있는 이름으로 관리 가능
즉 Named Native Query는
재사용 가능한 SQL 템플릿을 이름 붙여 등록해두는 방식으로 이해하면 된다.
13. JPQL과 Native Query 차이
이 둘은 목적이 다르다.
JPQL
- 엔티티 중심
- 객체지향적
- 필드명, 엔티티명 사용
- JPA 철학에 더 가깝다
Native Query
- SQL 중심
- DB 친화적
- 테이블명, 컬럼명 사용
- 복잡한 SQL에 강하다
즉 JPQL은 JPA의 기본 언어이고,
Native Query는 JPA 안에서 SQL을 직접 써야 할 때 쓰는 도구다.
14. Spring Data JPA란 무엇인가
이번 범위의 마지막은 Spring Data JPA의 시작 구조였다.
Spring Data JPA
JPA를 더 편리하게 사용하도록 도와주는 스프링의 추상화 기술
지금까지는:
- EntityManager 주입
- createQuery()
- persist()
- find()
같은 작업을 직접 다뤘다.
Spring Data JPA를 쓰면 이런 반복 작업을 훨씬 줄일 수 있다.
즉 목표는:
- Repository 코드 감소
- CRUD 작업 단순화
- 메서드 이름 기반 조회 같은 편의 기능 제공
이다.
15. 왜 Spring Data JPA를 쓰는가
JPA 자체도 JDBC보다 편하지만,
그래도 여전히 다음 같은 반복이 있다.
- EntityManager 주입
- 조회 쿼리 작성
- CRUD 메서드 반복 구현
Spring Data JPA는 이걸 더 줄인다.
즉 JPA가 “객체 중심 DB 처리”를 가능하게 해줬다면,
Spring Data JPA는 그 JPA 사용 코드까지 더 줄여주는 도구라고 보면 된다.
결국 실무에서는:
- 간단한 CRUD -> Spring Data JPA
- 조금 복잡한 조회 -> JPQL
- 더 복잡한 SQL -> Native Query 또는 다른 도구
처럼 자연스럽게 확장된다.
16. 이번 범위를 하나로 연결해서 보기
이번 학습을 흐름으로 정리하면 이렇다.
1. JPQL JOIN
엔티티 관계를 따라 조회를 확장한다.
2. FETCH JOIN
연관 엔티티를 한 번에 로딩해서 성능 문제를 줄인다.
3. Native Query
JPQL로 부족한 경우 직접 SQL을 쓴다.
4. Named Native Query
반복해서 쓸 SQL에 이름을 붙여 재사용한다.
5. Spring Data JPA
JPA 사용 자체를 더 간단하게 만든다.
즉 이번 범위는
JPA 조회 표현이 점점 더 현실적인 방향으로 확장되는 흐름으로 보면 된다.
17. 학습 흐름 정리
JPQL JOIN
- 내부 조인
- 외부 조인
- 컬렉션 조인
- Fetch Join
Native Query
- 엔티티 매핑 가능한 SQL 결과 처리
- 스칼라/집계 결과 처리
- 결과셋 매핑 구조 이해
- Named Native Query 등록과 사용
Spring Data JPA
- JPA 반복 코드 줄이기 위한 다음 단계
- Repository 중심 구조로 확장될 준비 단계
18. 핵심 정리
- JPQL의 JOIN은 테이블과 외래키가 아니라 엔티티와 연관관계 필드를 기준으로 작성한다.
- 내부 조인은 양쪽이 모두 연결된 데이터만 가져오고, 외부 조인은 연결되지 않은 쪽도 유지할 수 있다.
- 컬렉션 조인은 OneToMany 같은 컬렉션 연관관계를 따라가는 조회 방식이다.
- Fetch Join은 연관 엔티티를 한 번에 함께 로딩해서 추가 조회를 줄이기 위한 조인이다.
- Native Query는 JPA 안에서 SQL을 직접 작성해 실행하는 방식이다.
- Native Query는 복잡한 SQL이나 DB 전용 기능이 필요할 때 유용하다.
- Native Query 결과는 엔티티로 받을 수도 있고, 일부 값 조회 시 Object[]로 받을 수도 있다.
- @SqlResultSetMapping은 복잡한 SQL 결과를 JPA가 이해할 수 있게 매핑하는 설정이다.
- Named Native Query는 자주 쓰는 SQL에 이름을 붙여 재사용하는 방식이다.
- JPQL은 엔티티 중심, Native Query는 SQL 중심이라고 구분하면 이해가 쉽다.
- Spring Data JPA는 JPA 사용 코드를 더 줄이고 CRUD를 더 편하게 만들기 위한 스프링 기술이다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]Spring Data JPA로 메뉴 등록, 수정, 삭제 구현하기 (0) | 2026.06.18 |
|---|---|
| [TIL]Spring Data JPA로 메뉴 조회 API 구성하기 (0) | 2026.06.17 |
| [TIL]JPQL의 Projection, Paging, Group Function 정리 (0) | 2026.06.15 |
| [TIL]JPA 연관관계의 주인, JPQL, 그리고 지연 로딩 (0) | 2026.06.12 |
| [TIL]Spring + MyBatis의 @Transactional, 그리고 JPA 입문 개념 정리 (0) | 2026.06.09 |