JPQL 문법은 어떻게 될까?
JPQL 기본 문법
--
JPQL의 문법은 SQL과 거의 비슷하다. (똑같다고 생각해도 무방)
문법 형식
SELECT 컬럼(엔티티 필드) FROM 엔티티명 [AS 별칭]
[WHERE 조건]
[GROUP BY 그룹핑 기준]
[HAVING 그룹핑 조건]
[ORDER BY 정렬 기준]
예시 코드
String jpql = "SELECT m.name FROM Member m";
List<String> names = em.createQuery(jpql, String.class)
.getResultList();
- 엔티티와 속성은 대소문자 구분 O
- JPQL 키워드는 대소문자 구분 X
- 엔티티 객체를 대상으로 쿼리를 작성하는 것으로 데이블 이름이 아닌 엔티티 이름을 사용
- 별칭은 필수 (Member를 m으로 별칭 하는 것처럼 / AS는 생략 가능)
TypedQuery<T>, Query 반환 타입
- TypeQuery<T> : 반환 타입이 명확할 때 사용
- Query : 반환 타입이 명확하지 않을 때 사용
예시 코드
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
Query query = em.createQuery("SELECT m.username, m.age from Member m");
// 엔티티 리스트 조회 (TypedQuery)
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> members = query.getResultList();
// 결과: List<Member>
// 여러 개의 필드 조회 (Query)
Query query = em.createQuery("SELECT m.name, m.age FROM Member m");
List<Object[]> results = query.getResultList();
for (Object[] row : results) {
String name = (String) row[0];
Integer age = (Integer) row[1];
System.out.println("이름: " + name + ", 나이: " + age);
}
결과 조회 API
- getResultList() : 결과가 하나 이상일 때 리스트로 반환 (결과가 없으면 빈 리스트 반환)
- getSingleResult() : 결과가 정확히 하나인 단일 객체로 반환 (결과가 둘 이상이면 에러 발생)
(결과 없을 때 = NoResultException 발생 / 둘 이상일 때 = NonUniqueResultException 발생)
예시 코드
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
Member result = query.getSingleResult();
파라미터 바인딩
쿼리에 직접 값을 넣는 대신,
실행 시점에 값을 안전하게 주입하는 방법으로
이름, 위치 기준으로 바인딩이 가능하다.
이름 기반의 파라미터 바인딩 예시 코드 ( :기호를 사용 )
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class);
query.setParameter("username", "An"); //username에서 An인 값을 찾아온다.
Member singleResult = query.getSingleResult();
// 참고. 위 코드를 메서드 체인을 통해 깔끔하게 작성 가능
Member result = em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class)
.setParameter("username", "An")
.getSingledResult();
// 실행되는 SQL문
select
m.id as id,
m.age as age,
m.TEAM_ID as TEAM_ID,
m.username as username
from
Member m
where
m.username = An
- :name 처럼 이름을 지정하여 가독성 증가
- 순서를 신경 쓰지 않아도 되므로 유지보수성 증가
- 추천 O
위치 기반의 파라미터 바인딩 예시 코드 ( ?기호를 사용 )
Member result = em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class)
.setParameter(1, "An") // 1번째 파라미터에 "An" 대입
.getSingledResult();
- ?1, ?2 처럼 위치(순서)로 값을 바인딩
- 순서를 신경 써야 하므로 유지보수성이 낮음
- 추천 X
--
프로젝션 (Projection)
--
프로젝션은
SELECT 절에서 조회할 대상을 지정하는 것으로
엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입), DTO를 지정한다.
(DISTINCT 키워드로 중복 제거도 가능)
// m은 Member 엔티티를 의미 = 엔티티 프로젝션
SELECT m FROM Member m
// m.team은 Team 엔티티를 의미 = 엔티티 프로젝션
SELECT m.team FROM Member m
// m.address는 따로 만들어 놓은 임베디드를 의미 = 임베디드 타입 프로젝션
SELECT m.address FROM Member m
// m.username과 m.age는 String과 int 타입을 의미 = 스칼라 타입 프로젝션
SELECT m.username, m.age FROM Member m
// new 키워드를 사용하여 해당 엔티티의 DTO 생성자를 호출하여 DTO 객체로 직접 변환하여 조회 = DTO 프로젝션
SELECT new com.example.MemberDTO(m.name, m.age) FROM Member m
new 키워드를 통해 JPQL에서 DTO 객체로 직접 변환하여 조회하는 방식은
단순 값을 DTO로 바로 조회하는 방법으로 DTO 타입으로 조회한다.
이를 작성할 때 패키지 명을 포함한 전체 클래스 명을 입력해야 하며,
순서와 타입이 일치하는 생성자가 해당 DTO에 필요하다.
가독성이 좋고, 타입 캐스팅이 필요 없어 가장 깔끔한 방법이다.
--
페이징 API
--
JPA에서 대량의 데이터를 효율적으로 조회하기 위해 제공하는 기능으로
결과의 개수를 제한하고 특정 위치에서 조회할 수 있다.
(ex. 게시판에서 총 100개의 데이터가 존재하면 이를 한 페이지당 10개씩 총 10페이지로 나누고 원하는 페이지의 데이터만 조회 가능)
JPA에서는 페이징을 2개의 API로 모두 추상화했다.
페이징 API 메서드 종류

위 두 API를 사용하여 몇 번째 부터 몇 개의 데이터를 조회할지 정의할 수 있다.
예시 코드
// 10번째 데이터부터 총 20개의 데이터를 가져온다.
String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setFirstResult(10)
.setMaxResults(20)
.getResultList();
장점
- 대량 데이터 조회 시 성능 최적화
- 필요한 데이터만 조회하므로 불필요한 리소스 낭비 방지
- 메모리 사용량 감소
--
조인 (JOIN)
--
JPQL의 조인은
기존 SQL의 조인과 실행되는 것이 동일하지만
엔티티를 중심으로 동작을 하다보니 객체 스타일로 문법을 작성하는 차이점이 존재한다.
즉, 엔티티 객체 간의 관계를 기준으로 조인을 수행한다.
예시 코드
// 내부 조인 (m과 m안에 존재하는 team을 t로 조인)
SELECT m FROM Member m [INNER] JOIN m.team t
// 외부 조인
SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
// 세타 조인 (서로 관계없는 것 끼리 조인 가능)
SELECT count(m) FROM Member m, Team t WHERE m.username = t.name
대괄호로 표시한 INNER, OUTER는 생략 가능
ON 절
ON 절을 활용한 조인을 사용하면
조인할 때 조인 대상을 미리 필터링 할 수 있으며,
연관관계없는 엔티티를 내부, 외부 조인도 가능하다.
예시 코드
// 조인 대상을 미리 필터링 후 조인
// (Member와 Team을 조인하면서, Team의 name이 A인 Team만 조인)
// JPQL
SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
// SQL
SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID=t.id and t.name='A'
// 연관관계가 없는 엔티티를 외부 조인
// Member의 username과 Team의 name이 같은 대상의 외부 조인
// JPQL
SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
// SQL
SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
--
서브 쿼리 (Subquery)
--
JPQL에서도 서브 쿼리(쿼리 안에 쿼리)를 지원하지만
JPQL에서 사용하는 서브 쿼리는 한계가 존재한다.
- JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
(SELECT 절에서도 가능 = 하이버네이트에서 지원) - FROM 절의 서브 쿼리는 하이버네이트 6부터 지원
서브 쿼리에서 지원하는 함수
- [NOT] EXISTS (서브쿼리) : 서브 쿼리에 결과가 존재하면 참
- {ALL | ANY | SOME}(서브쿼리) : ALL : 모두 만족하면 참 / ANY, SOME : 하나라도 만족하면 참
- [NOT] IN (서브쿼리) : 서브 쿼리의 결과 중 하나라도 같은 것이 존재하면 해당하는 데이터 조회
예시 코드
// "팀A" 소속인 회원 조회
select m from Member m where exists (select t from m.team t where t.name = '팀A')
// 전체 상품의 각각 재고보다 주문량이 많은 주문을 조회
select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)
// 어떤 팀이든 팀에 소속된 회원 조회
select m from Member m where m.team = ANY (select t from Team t)
--
JPQL 각 타입 표현식 & 기타식 & 조건 CASE 식 정리
--
타입 표현
- 문자 : 'Hello', 'she''s' ('를 표현하기 위해서는 ''두 번 작성)
- 숫자 : 10L (= Long), 10D (= Double), 10F (= Float)
- Boolean : TRUE, FALSE
- ENUM : 패키지경로.클래스명 (패키지명 포함해서 작성)
- 엔티티 : TYPE(m) = Member
기타 표현
- SQL 문법과 동일
- EXISTS, IN
- AND, OR, NOT
- =, >, >=, <, <=, <>
- BETWEEN, LIKE, IS NULL
조건 (CASE 식)
기본 CASE 식 (범위에 해당)
// m.age가 10이하이면 "학생요금", 60이상이면 "경로요금", 나머지는 "일반요금"
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
단순 CASE 식 (특정 값과 동일)
// t.name이 "팀A" 이면 "인센티브110%" "팀B"이면 "인센티브120%" 나머지는 "인센티브105%"
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t
COALESCE (하나씩 조회하여 null이 아니면 반환)
// username이 없으면 ‘이름 없는 회원’을 반환, 있으면 username 반환
select coalesce(m.username,'이름 없는 회원') from Member m
NULLIF (두 값이 같으면 null 반환, 다르면 첫 번째 값 반환)
// username이 ‘관리자’면 null 반환, 아닌 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m
--
참고 및 출처
'JPA' 카테고리의 다른 글
객체지향 쿼리 언어(OOQL) 종류 (0) | 2025.04.05 |
---|---|
JPA의 데이터 타입 (0) | 2025.04.04 |
영속성 전이 (CASCADE) (+ 고아 객체) (0) | 2025.04.03 |
즉시 로딩 및 지연 로딩 (+ JPA의 프록시) (1) | 2025.04.02 |
상속관계 매핑 (+@MappedSuperclass) (1) | 2025.04.01 |
JPQL 문법은 어떻게 될까?
JPQL 기본 문법
--
JPQL의 문법은 SQL과 거의 비슷하다. (똑같다고 생각해도 무방)
문법 형식
SELECT 컬럼(엔티티 필드) FROM 엔티티명 [AS 별칭]
[WHERE 조건]
[GROUP BY 그룹핑 기준]
[HAVING 그룹핑 조건]
[ORDER BY 정렬 기준]
예시 코드
String jpql = "SELECT m.name FROM Member m";
List<String> names = em.createQuery(jpql, String.class)
.getResultList();
- 엔티티와 속성은 대소문자 구분 O
- JPQL 키워드는 대소문자 구분 X
- 엔티티 객체를 대상으로 쿼리를 작성하는 것으로 데이블 이름이 아닌 엔티티 이름을 사용
- 별칭은 필수 (Member를 m으로 별칭 하는 것처럼 / AS는 생략 가능)
TypedQuery<T>, Query 반환 타입
- TypeQuery<T> : 반환 타입이 명확할 때 사용
- Query : 반환 타입이 명확하지 않을 때 사용
예시 코드
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
Query query = em.createQuery("SELECT m.username, m.age from Member m");
// 엔티티 리스트 조회 (TypedQuery)
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> members = query.getResultList();
// 결과: List<Member>
// 여러 개의 필드 조회 (Query)
Query query = em.createQuery("SELECT m.name, m.age FROM Member m");
List<Object[]> results = query.getResultList();
for (Object[] row : results) {
String name = (String) row[0];
Integer age = (Integer) row[1];
System.out.println("이름: " + name + ", 나이: " + age);
}
결과 조회 API
- getResultList() : 결과가 하나 이상일 때 리스트로 반환 (결과가 없으면 빈 리스트 반환)
- getSingleResult() : 결과가 정확히 하나인 단일 객체로 반환 (결과가 둘 이상이면 에러 발생)
(결과 없을 때 = NoResultException 발생 / 둘 이상일 때 = NonUniqueResultException 발생)
예시 코드
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
Member result = query.getSingleResult();
파라미터 바인딩
쿼리에 직접 값을 넣는 대신,
실행 시점에 값을 안전하게 주입하는 방법으로
이름, 위치 기준으로 바인딩이 가능하다.
이름 기반의 파라미터 바인딩 예시 코드 ( :기호를 사용 )
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class);
query.setParameter("username", "An"); //username에서 An인 값을 찾아온다.
Member singleResult = query.getSingleResult();
// 참고. 위 코드를 메서드 체인을 통해 깔끔하게 작성 가능
Member result = em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class)
.setParameter("username", "An")
.getSingledResult();
// 실행되는 SQL문
select
m.id as id,
m.age as age,
m.TEAM_ID as TEAM_ID,
m.username as username
from
Member m
where
m.username = An
- :name 처럼 이름을 지정하여 가독성 증가
- 순서를 신경 쓰지 않아도 되므로 유지보수성 증가
- 추천 O
위치 기반의 파라미터 바인딩 예시 코드 ( ?기호를 사용 )
Member result = em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class)
.setParameter(1, "An") // 1번째 파라미터에 "An" 대입
.getSingledResult();
- ?1, ?2 처럼 위치(순서)로 값을 바인딩
- 순서를 신경 써야 하므로 유지보수성이 낮음
- 추천 X
--
프로젝션 (Projection)
--
프로젝션은
SELECT 절에서 조회할 대상을 지정하는 것으로
엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입), DTO를 지정한다.
(DISTINCT 키워드로 중복 제거도 가능)
// m은 Member 엔티티를 의미 = 엔티티 프로젝션
SELECT m FROM Member m
// m.team은 Team 엔티티를 의미 = 엔티티 프로젝션
SELECT m.team FROM Member m
// m.address는 따로 만들어 놓은 임베디드를 의미 = 임베디드 타입 프로젝션
SELECT m.address FROM Member m
// m.username과 m.age는 String과 int 타입을 의미 = 스칼라 타입 프로젝션
SELECT m.username, m.age FROM Member m
// new 키워드를 사용하여 해당 엔티티의 DTO 생성자를 호출하여 DTO 객체로 직접 변환하여 조회 = DTO 프로젝션
SELECT new com.example.MemberDTO(m.name, m.age) FROM Member m
new 키워드를 통해 JPQL에서 DTO 객체로 직접 변환하여 조회하는 방식은
단순 값을 DTO로 바로 조회하는 방법으로 DTO 타입으로 조회한다.
이를 작성할 때 패키지 명을 포함한 전체 클래스 명을 입력해야 하며,
순서와 타입이 일치하는 생성자가 해당 DTO에 필요하다.
가독성이 좋고, 타입 캐스팅이 필요 없어 가장 깔끔한 방법이다.
--
페이징 API
--
JPA에서 대량의 데이터를 효율적으로 조회하기 위해 제공하는 기능으로
결과의 개수를 제한하고 특정 위치에서 조회할 수 있다.
(ex. 게시판에서 총 100개의 데이터가 존재하면 이를 한 페이지당 10개씩 총 10페이지로 나누고 원하는 페이지의 데이터만 조회 가능)
JPA에서는 페이징을 2개의 API로 모두 추상화했다.
페이징 API 메서드 종류

위 두 API를 사용하여 몇 번째 부터 몇 개의 데이터를 조회할지 정의할 수 있다.
예시 코드
// 10번째 데이터부터 총 20개의 데이터를 가져온다.
String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setFirstResult(10)
.setMaxResults(20)
.getResultList();
장점
- 대량 데이터 조회 시 성능 최적화
- 필요한 데이터만 조회하므로 불필요한 리소스 낭비 방지
- 메모리 사용량 감소
--
조인 (JOIN)
--
JPQL의 조인은
기존 SQL의 조인과 실행되는 것이 동일하지만
엔티티를 중심으로 동작을 하다보니 객체 스타일로 문법을 작성하는 차이점이 존재한다.
즉, 엔티티 객체 간의 관계를 기준으로 조인을 수행한다.
예시 코드
// 내부 조인 (m과 m안에 존재하는 team을 t로 조인)
SELECT m FROM Member m [INNER] JOIN m.team t
// 외부 조인
SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
// 세타 조인 (서로 관계없는 것 끼리 조인 가능)
SELECT count(m) FROM Member m, Team t WHERE m.username = t.name
대괄호로 표시한 INNER, OUTER는 생략 가능
ON 절
ON 절을 활용한 조인을 사용하면
조인할 때 조인 대상을 미리 필터링 할 수 있으며,
연관관계없는 엔티티를 내부, 외부 조인도 가능하다.
예시 코드
// 조인 대상을 미리 필터링 후 조인
// (Member와 Team을 조인하면서, Team의 name이 A인 Team만 조인)
// JPQL
SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
// SQL
SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID=t.id and t.name='A'
// 연관관계가 없는 엔티티를 외부 조인
// Member의 username과 Team의 name이 같은 대상의 외부 조인
// JPQL
SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
// SQL
SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
--
서브 쿼리 (Subquery)
--
JPQL에서도 서브 쿼리(쿼리 안에 쿼리)를 지원하지만
JPQL에서 사용하는 서브 쿼리는 한계가 존재한다.
- JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
(SELECT 절에서도 가능 = 하이버네이트에서 지원) - FROM 절의 서브 쿼리는 하이버네이트 6부터 지원
서브 쿼리에서 지원하는 함수
- [NOT] EXISTS (서브쿼리) : 서브 쿼리에 결과가 존재하면 참
- {ALL | ANY | SOME}(서브쿼리) : ALL : 모두 만족하면 참 / ANY, SOME : 하나라도 만족하면 참
- [NOT] IN (서브쿼리) : 서브 쿼리의 결과 중 하나라도 같은 것이 존재하면 해당하는 데이터 조회
예시 코드
// "팀A" 소속인 회원 조회
select m from Member m where exists (select t from m.team t where t.name = '팀A')
// 전체 상품의 각각 재고보다 주문량이 많은 주문을 조회
select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)
// 어떤 팀이든 팀에 소속된 회원 조회
select m from Member m where m.team = ANY (select t from Team t)
--
JPQL 각 타입 표현식 & 기타식 & 조건 CASE 식 정리
--
타입 표현
- 문자 : 'Hello', 'she''s' ('를 표현하기 위해서는 ''두 번 작성)
- 숫자 : 10L (= Long), 10D (= Double), 10F (= Float)
- Boolean : TRUE, FALSE
- ENUM : 패키지경로.클래스명 (패키지명 포함해서 작성)
- 엔티티 : TYPE(m) = Member
기타 표현
- SQL 문법과 동일
- EXISTS, IN
- AND, OR, NOT
- =, >, >=, <, <=, <>
- BETWEEN, LIKE, IS NULL
조건 (CASE 식)
기본 CASE 식 (범위에 해당)
// m.age가 10이하이면 "학생요금", 60이상이면 "경로요금", 나머지는 "일반요금"
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
단순 CASE 식 (특정 값과 동일)
// t.name이 "팀A" 이면 "인센티브110%" "팀B"이면 "인센티브120%" 나머지는 "인센티브105%"
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t
COALESCE (하나씩 조회하여 null이 아니면 반환)
// username이 없으면 ‘이름 없는 회원’을 반환, 있으면 username 반환
select coalesce(m.username,'이름 없는 회원') from Member m
NULLIF (두 값이 같으면 null 반환, 다르면 첫 번째 값 반환)
// username이 ‘관리자’면 null 반환, 아닌 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m
--
참고 및 출처
'JPA' 카테고리의 다른 글
객체지향 쿼리 언어(OOQL) 종류 (0) | 2025.04.05 |
---|---|
JPA의 데이터 타입 (0) | 2025.04.04 |
영속성 전이 (CASCADE) (+ 고아 객체) (0) | 2025.04.03 |
즉시 로딩 및 지연 로딩 (+ JPA의 프록시) (1) | 2025.04.02 |
상속관계 매핑 (+@MappedSuperclass) (1) | 2025.04.01 |