1. 학습 주제
이번에는 JPA에서 엔티티를 단순 조회하는 수준을 넘어서,
JPQL로 어떤 값만 뽑아오는 방식, 페이지 단위 조회, 집계 함수와 그룹화를 다루는 흐름까지 이어졌다.
즉 핵심은 다음 세 가지였다.
- Projection: 무엇을 조회할 것인가
- Paging: 몇 개를, 어디부터 가져올 것인가
- Group Function: 여러 행을 묶어서 계산할 것인가
이 범위는 “JPQL도 결국 조회 언어인데, SQL처럼 다양한 조회 표현이 가능하다”는 점을 이해하는 데 의미가 있었다.
2. JPQL은 다시 무엇을 기준으로 쓰는가
JPQL은 SQL처럼 보이지만 기준이 다르다.
SQL은:
- 테이블 이름
- 컬럼 이름
을 기준으로 쓴다.
JPQL은:
- 엔티티 이름
- 엔티티 필드 이름
을 기준으로 쓴다.
예를 들어 SQL이라면 tbl_menu, menu_name을 쓰겠지만,
JPQL은 Section03Menu, menuName처럼 엔티티와 필드를 기준으로 쓴다.
즉 JPQL은
데이터베이스용 SQL이 아니라 객체용 조회 언어라고 이해해야 한다.
3. Projection이란 무엇인가
Projection은
조회 결과로 무엇을 가져올지 선택하는 것이다.
즉 “조회는 한다”는 점은 같지만,
그 결과를 어떤 형태로 받을지에 따라 projection 종류가 달라진다.
이번에 본 것은 크게 세 가지였다.
- 엔티티 프로젝션
- 스칼라 프로젝션
- DTO 프로젝션
이 차이를 구분하는 것이 핵심이다.
4. 엔티티 프로젝션
엔티티 프로젝션은
엔티티 자체를 조회하는 방식이다.
예를 들어:
SELECT m FROM Section03Menu m
처럼 쓰면, 결과는 Menu 엔티티 객체 리스트가 된다.
이 방식의 특징은:
- 조회 결과가 엔티티다
- 영속성 컨텍스트 관리 대상이 될 수 있다
- 조회 후 필드 변경 시 변경 감지와 연결될 수 있다
즉 엔티티 프로젝션은
값만 뽑는 게 아니라, JPA가 관리하는 객체를 가져오는 방식이다.
5. 엔티티 프로젝션과 변경 감지
이번 코드에서 서비스 계층 메서드에 @Transactional이 붙어 있고,
조회한 메뉴의 이름을 바꾸는 흐름이 있었다.
이게 중요한 이유는 엔티티 프로젝션으로 가져온 결과가
영속성 컨텍스트에서 관리되는 엔티티이기 때문이다.
즉:
- 엔티티 조회
- 영속 상태 엔티티 반환
- 필드 값 변경
- 트랜잭션 commit 시점에 변경 감지
- DB 반영 가능
이 흐름이 성립한다.
그래서 엔티티 프로젝션은
단순 조회 결과가 아니라
상태가 관리되는 객체 조회로 이해해야 한다.
6. 스칼라 프로젝션
스칼라 프로젝션은
엔티티 전체가 아니라 특정 값들만 조회하는 방식이다.
예를 들어:
SELECT c.categoryCode, c.categoryName FROM Section03Category c
처럼 조회하면:
- 카테고리 코드
- 카테고리 이름
두 컬럼 값만 가져오게 된다.
이 경우 반환 타입은 보통 Object[]가 된다.
왜냐하면 여러 값을 한 줄씩 묶어서 받아야 하기 때문이다.
즉 스칼라 프로젝션은
엔티티가 아니라 컬럼값 중심 조회라고 보면 된다.
7. 스칼라 프로젝션의 특징
스칼라 프로젝션은 필요한 값만 가져올 수 있어서 가볍지만,
단점도 있다.
- 반환값이 Object[] 형태라 가독성이 떨어질 수 있다
- 인덱스로 꺼내야 해서 실수 가능성이 있다
- 의미가 바로 드러나지 않는다
즉 간단한 값 몇 개를 빨리 조회할 때는 쓸 수 있지만,
구조가 복잡해질수록 DTO 프로젝션 쪽이 더 읽기 좋아진다.
8. DTO 프로젝션과 new 문법
DTO 프로젝션은
조회 결과를 바로 DTO 객체로 만들어서 받는 방식이다.
예를 들어:
SELECT new ...CategoryInfo(c.categoryCode, c.categoryName)
FROM Section03Category c
이렇게 쓰면 JPQL 조회 결과가 CategoryInfo 객체 리스트로 바로 반환된다.
즉:
- 스칼라 프로젝션은 Object[]
- DTO 프로젝션은 내가 원하는 DTO 타입
으로 받을 수 있다.
이 방식의 장점은 다음과 같다.
- 의미가 분명하다
- 타입이 안전하다
- 코드 가독성이 좋다
- 결과를 바로 전달하기 좋다
즉 DTO 프로젝션은
조회 결과를 목적에 맞는 응답용 객체로 바로 만드는 방식이라고 보면 된다.
9. 엔티티 프로젝션 vs DTO 프로젝션
이 둘은 꽤 다르다.
엔티티 프로젝션
- 엔티티를 조회
- 영속성 컨텍스트 관리 대상
- 변경 감지 가능
DTO 프로젝션
- DTO를 조회 결과로 생성
- 영속성 컨텍스트 관리 대상 아님
- 조회 전용 응답에 적합
즉 “조회 후 수정까지 연결되는가”가 큰 차이점이다.
실무에서는 보통:
- 수정 가능성이 있는 내부 처리 -> 엔티티 프로젝션
- 화면 응답, 목록 출력 -> DTO 프로젝션
으로 많이 나뉜다.
10. Paging이란 무엇인가
Paging은
조회 결과를 한 번에 전부 가져오지 않고, 일부 범위만 잘라서 가져오는 것이다.
즉 게시판, 상품 목록, 회원 목록처럼 데이터가 많을 때
필요한 구간만 가져오는 방식이다.
JPQL에서는 setFirstResult()와 setMaxResults()로 이를 처리할 수 있다.
11. setFirstResult()와 setMaxResults()
이 둘의 역할은 다음과 같다.
setFirstResult(offset)
조회 시작 위치를 정한다.
setMaxResults(limit)
가져올 데이터 개수를 정한다.
즉 조합하면:
- 몇 번째부터
- 몇 개를 가져올지
정할 수 있다.
예를 들어:
- offset = 10
- limit = 5
라면 11번째 데이터부터 5개를 가져오는 감각으로 이해하면 된다.
즉 JPQL의 페이징은
SQL의 LIMIT/OFFSET과 비슷한 역할을 객체 조회 문법에서 제공하는 것이다.
12. 왜 Paging이 중요한가
전체 데이터를 매번 다 가져오면 비효율적이다.
예:
- 메뉴 10만 개
- 게시글 100만 개
- 주문 내역 수십만 개
이걸 한 번에 다 메모리에 올리면:
- 조회 속도 저하
- 메모리 낭비
- 화면 렌더링 비효율
문제가 생긴다.
그래서 페이징은
실제 서비스에서 거의 기본적으로 필요한 조회 전략이다.
즉 “조회는 되는데 느리다” 문제를 피하려면
필요한 만큼만 가져오는 사고가 중요하다.
13. Group Function이란 무엇인가
Group Function은 여러 행을 대상으로 계산하는 함수들이다.
대표적으로:
- COUNT
- SUM
- AVG
- MAX
- MIN
등이 있다.
즉 “한 행을 읽는 것”이 아니라,
여러 행을 묶어서 계산 결과를 얻는 조회라고 보면 된다.
14. COUNT
예제에서는 특정 카테고리에 속한 메뉴 개수를 구했다.
예:
SELECT COUNT(m.menuCode)
FROM Section05Menu m
WHERE m.categoryCode = :categoryCode
즉 조건에 맞는 메뉴가 몇 개인지 세는 것이다.
이건 실무에서도 매우 자주 쓰인다.
예:
- 특정 카테고리 상품 수
- 특정 사용자의 게시글 수
- 특정 상태의 주문 수
즉 COUNT는
개수를 세는 집계 함수다.
15. SUM과 결과 없음
SUM도 예제에 들어 있었다.
예를 들어 특정 카테고리 메뉴 가격 합계를 구하는 방식이다.
여기서 중요한 포인트는 COUNT와 다른 집계 함수의 차이이다.
- COUNT는 조건에 맞는 행이 없어도 보통 0을 반환한다
- SUM, AVG 같은 함수는 조건에 맞는 행이 없으면 null이 나올 수 있다
즉 집계 함수는 같아 보여도
“결과 없음” 상황에서 반환값이 다를 수 있다는 점을 알아야 한다.
이건 나중에 null 처리할 때 중요하다.
16. GROUP BY
GROUP BY는
같은 값끼리 묶어서 그룹별로 계산하는 방식이다.
예를 들어 카테고리별 메뉴 가격 합계를 보고 싶다면:
- 카테고리 코드를 기준으로 묶고
- 각 그룹의 SUM(menuPrice)를 계산
하는 식이다.
즉 GROUP BY는
집계의 기준 축을 만드는 역할을 한다.
17. HAVING
HAVING은 그룹화된 결과에 조건을 거는 것이다.
예를 들어:
- 카테고리별 합계 금액을 계산하고
- 그 합계가 50000 이상인 카테고리만 보고 싶다
면 HAVING을 쓴다.
즉:
- WHERE는 그룹화 전 개별 행 필터링
- HAVING은 그룹화 후 집계 결과 필터링
이다.
이 구분은 SQL과 JPQL 모두에서 매우 중요하다.
18. Object[]가 다시 나오는 이유
그룹 함수와 GROUP BY 결과는 보통 한 줄에 여러 값이 같이 나온다.
예:
- 카테고리 코드
- 합계 금액
그래서 결과 타입이 보통 Object[]가 된다.
이건 스칼라 프로젝션 때와 비슷하다.
즉 여러 값을 한 번에 조회하면
엔티티 하나가 아니라 Object[] 형태가 자주 등장한다는 점을 기억하면 좋다.
19. Projection, Paging, Group Function을 한 흐름으로 보면
이 세 개는 따로 보이지만 사실 모두 “조회 최적화”와 연결된다.
Projection
무엇을 가져올지 정한다.
Paging
얼마나 가져올지 정한다.
Group Function
가져온 데이터를 어떻게 계산할지 정한다.
즉 JPQL 조회를 설계할 때는 단순히:
- 데이터 조회
가 아니라
- 어떤 형태로
- 몇 개를
- 어떤 기준으로 묶어서
- 어떤 계산값으로
가져올지까지 생각해야 한다.
20. 언제 엔티티를 그대로 조회하고, 언제 DTO로 뽑는가
복습 포인트로 정리하면 이렇다.
엔티티 그대로 조회
- 이후 수정 가능성이 있다
- 영속성 컨텍스트 관리가 필요하다
- 도메인 로직 안에서 사용할 목적이다
DTO로 조회
- 화면 응답용이다
- 필요한 값만 가볍게 가져오고 싶다
- 조회 전용 결과가 목적이다
즉 “조회 결과가 관리 대상이어야 하는가”를 기준으로 보면 구분이 쉬워진다.
21. 학습 흐름 정리
21.1 Projection
- 엔티티 프로젝션: 엔티티 자체 조회
- 스칼라 프로젝션: 특정 값들만 조회
- DTO 프로젝션: new 문법으로 DTO 생성
21.2 Paging
- setFirstResult()로 시작 위치 지정
- setMaxResults()로 개수 제한
- 대량 데이터를 나눠서 조회하는 방식 이해
21.3 Group Function
- COUNT, SUM 같은 집계 함수 사용
- GROUP BY로 그룹화
- HAVING으로 집계 결과 필터링
- 결과 없음일 때 COUNT와 다른 함수의 차이 이해
22. 핵심 정리
- JPQL은 엔티티와 필드명을 기준으로 조회하는 객체지향 쿼리 언어이다.
- Projection은 조회 결과로 무엇을 가져올지 결정하는 방식이다.
- 엔티티 프로젝션은 엔티티 자체를 조회하며 영속성 컨텍스트 관리 대상이 될 수 있다.
- 스칼라 프로젝션은 특정 컬럼값만 조회하며 보통 Object[]로 받는다.
- DTO 프로젝션은 new 문법으로 조회 결과를 원하는 DTO 객체로 바로 만들 수 있다.
- 페이징은 많은 데이터를 한 번에 다 가져오지 않고 필요한 범위만 조회하는 방식이다.
- setFirstResult()는 시작 위치, setMaxResults()는 조회 개수를 지정한다.
- 집계 함수는 여러 행을 대상으로 계산하며 COUNT, SUM, AVG 등이 있다.
- COUNT는 결과가 없어도 0이 나올 수 있지만, 다른 집계 함수는 null이 될 수 있다.
- GROUP BY는 그룹 단위 계산을, HAVING은 그룹 결과 조건 필터링을 담당한다.
- 조회 후 수정까지 이어질 가능성이 있으면 엔티티 프로젝션, 조회 전용 응답이면 DTO 프로젝션이 더 적합하다.
복습할 때는 이 세 문장만 먼저 떠올리면 흐름이 빨리 잡힌다.
- Projection은 무엇을 가져올지 정한다.
- Paging은 얼마나 가져올지 정한다.
- Group Function은 가져온 데이터를 어떻게 계산할지 정한다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]Spring Data JPA로 메뉴 조회 API 구성하기 (0) | 2026.06.17 |
|---|---|
| [TIL]JPQL JOIN, Native Query, 그리고 Spring Data JPA 시작 (0) | 2026.06.16 |
| [TIL]JPA 연관관계의 주인, JPQL, 그리고 지연 로딩 (0) | 2026.06.12 |
| [TIL]Spring + MyBatis의 @Transactional, 그리고 JPA 입문 개념 정리 (0) | 2026.06.09 |
| [TIL]MyBatis 동적 쿼리와 Spring MyBatis 기본 흐름 (0) | 2026.06.08 |