Home JPQL (JPQL 기본 문법)
Post
Cancel

JPQL (JPQL 기본 문법)

JPA는 다양한 쿼리 방법을 지원한다.

  • JPQL **
  • JPA Criteria
    • 자바 코드로 쿼리를 작성하기 때문에 오류를 잡아내기 쉽다.
    • 동적쿼리 작성이 쉽다.
    • 하지만 코드가 복잡하고 알아보기 힘들어 유지보수 작업이 힘들다.
  • QueryDSL **
    • 동적쿼리 작성에 장점이 있다.
    • 단순하고 쉽다.
  • 네이티브 SQL
    • JPA가 제공하는 SQL을 직접 사용하는 기능.
    • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능을 사용할 때 사용한다.
  • JDBC API 직접 사용, MyBatis, SpringJdbcTemplate를 JPA와 함께 사용
    • 영속성 컨텍스트를 적절한 시점에 강제로 플러시 해야한다.
      • JPA를 우회해 SQL을 실행하기 직전에 영속성 컨텍스트 수동 플러시.

주로 JPQL + QueryDSL을 사용하는 것을 권장한다.

JPQL

JPA를 사용하면 엔티티 객체를 중심으로 개발하게 된다.

이 부분에서 문제가 발생한다. 바로 검색 쿼리이다. 검색을 할 때에도 테이블이 아닌 엔티티 객체를 대상으로 검색을 하게 된다.

하지만 모든 DB 데이터를 객체로 변환해 검색한다는 것은 모든 데이터를 전부 메모리에 올려야하기 때문에 불가능하다. 따라서 애플리케이션이 필요한 데이터만을 DB에서 불러오기 위해 검색 조건이 있는 SQL이 필요하게 된다.

그래서 JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공하게 된다.

  • SQL과 문법이 유사, SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN을 지원.
  • JPQL은 엔티티 객체를 대상으로, SQL은 데이터베이스 테이블을 대상으로 쿼리하는 것.
  • 테이블이 아닌 객체를 대상으로 검색을 하게 된다.
  • 최종적으로는 SQL로 변환되어 실행된다.

JPQL은 SQL을 추상화하여 특정 데이터베이스 SQL에 의존하지 않는 것이 특징이다.

JPQL 기본 문법 및 기능

위 구조를 기준으로 설명한다.

기본

1
select m from Member as m where m.age > 18
  • 위에서의 Member는 DB 테이블이 아닌 객체이다.
  • 엔티티와 속성은 대소문자를 구분한다.
  • JPQL 키워드는 대소문자를 구분하지 않는다. (SELECT, from, where)
  • 별칭이 필수이다. (m)
    • as는 생략이 가능하다.

집합과 정렬

  • GROUP BY, HAVING
  • ORDER BY

TypeQuery, Query

  • TypeQuery
    • 반환 타입이 명확할 때 사용한다.
  • Query
    • 반환 타입이 명확하지 않을 때 사용한다.
1
2
TypedQuery<Member> query = 
	em.createQuery("SELECT m FROM Member m", Member.class);
1
2
Query query = 
	em.createQuery("SELECT m.username, m.age from Member m");

결과 조회 API

  • query.getResultList()
    • 결과가 하나 이상일 때, 리스트로 반환한다.
    • 결과가 없으면 빈 리스트를 반환한다.
  • query.getSingleResult()
    • 결과가 정확히 하나일 때 단일 객체를 반환한다.
    • 결과가 없으면 javax.persistence.NoResultException 발생
    • 둘 이상이면 javax.persistence.NonUniqueResultException 발생
      • 하나의 결과인 것을 확신할 때 사용해야 한다.
1
2
3
4
TypedQuery<Member> query = 
	em.createQuery("SELECT m FROM Member m", Member.class);

List<Member> result = query.getResultList();

파라미터 바인딩

1
2
3
("SELECT m FROM Member m where m.username=:username");

query.setParameter("username", usernameParam);

: 를 사용해 파라미터 바인딩이 가능하다.

프로젝션

SELECT 절에 조회할 대상을 지정하는 것을 의미한다.

  • 프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입)
1
2
3
4
SELECT m FROM Member m -> 엔티티 프로젝션
SELECT m.team FROM Member m -> 엔티티 프로젝션
SELECT m.address FROM Member m -> 임베디드 타입 프로젝션
SELECT m.username, m.age FROM Member m -> 스칼라 타입 프로젝션
  • DISTINCT로 중복을 제거한다.

엔티티 프로젝션과 영속성 컨텍스트

1
2
3
4
5
6
//...
em.persist(member);

List<Member> result = 
	em.createQuery("select m from Member m", Member.class)
		.getResultList();

이렇게 가져온 result의 Member들은 영속성 컨텍스트에 관리가 될까?

1
2
Member findMember = result.get(0);
findMember.setAge(20);

위 코드의 실행 결과를 보면 DB에서 age가 20으로 바뀐 것을 볼 수 있다.

엔티티 프로젝션은 영속성 컨텍스트에 관리가 된다는 뜻이다.

여러 값 조회

1
SELECT m.username, m.age FROM Member m

위와 같은 스칼라 타입 프로젝션에서 서로 다른 타입의 값들을 가져오는데 어떻게 가져올까?

  • Query 타입으로 조회 ```java List result = em.createQuery(“select m.username, m.age from Member m”) .getResultList();

Object o = result.get(0); Object[] resultList = (Object[]) o;

1
2
3
4
5
- Object[] 타입으로 조회
```java
List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m")
	.getResultList();
  • new 명령어를 통한 조회
    • 단순 값을 DTO로 바로 조회하는 방식
    • 패키지 명을 포함한 전체 클래스 명 입력 필요
    • 순서와 타입이 일치하는 생성자 필요
1
2
List<MemberDTO> result = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class);
	.getResultList();

페이징 API

JPA는 페이징을 아래 두 API로 추상화 한다.

  • setFirstResult(int startPosition)
    • 조회 시작 위치 (0부터 시작)
  • setMaxResults(int maxResult)
    • 조회할 데이터 수
1
2
3
4
em.createQuery("select m from Member m order by m.age desc", Member.class)
	.setFristResult(0)
	.setMaxResults(10)
	.getResultList();

출력해보면 위와 같은 결과를 간단하게 뽑아낼 수 있다.

복잡한 페이징 쿼리를 알아서 작성해준다.

조인

  • 내부 조인
    • 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
    • 서로 연관관계가 없는 테이블의 데이터를 조인할 때 사용.
    • 위의 예시로는 username과 team 이름이 같은 것이 조건.

조인 - ON절

ON절을 활용하는 조인이 있다.

  • 조인 대상 필터링 가능
1
SELECT m,t FROM Member m LEFT JOIN m.team t on t.name ='A'
  • 연관관계 없는 엔티티를 외부 조인 가능
1
SELECT m,t FROM Member m LEFT JOIN Team t on m.username = t.name

회원의 이름과 팀의 이름이 같은 대상만 외부 조인.

서브 쿼리

쿼리 내부에 또 다른 쿼리를 섞어 사용하는 것. SQL과 같다.

  • ex) 나이가 평균보다 많은 회원
1
SELECT m FROM Member m where m.age > (SELECT avg(m2.age) from Member m2)
  • ex) 한 건이라도 주문한 고객
1
SELECT m from Member m where (SELECT count(o) from Order o where m = o.member) > 0

서브 쿼리 지원 함수

  • [NOT] EXISTS + subquery : 서브 쿼리에 결과가 존재하면 true
    • ALL : 모두 만족하면 true
    • ANY, SOME : 조건을 하나라도 만족하면 true
  • [NOT] IN + subquery : 서브 쿼리의 결과 중 하나라도 같은 것이 있으면 true

ex) 팀 A 소속인 회원

1
2
SELECT m FROM Member m 
	WHERE exists (SELECT t FROM m.team t where t.name = 'A')

ex) 전체 상품 각각의 재고보다 주문량이 많은 주문들

1
2
SELECT o FROM Order o
	WHERE o.orderAmount > ALL(SELECT p.stockAmount FROM Product p)

ex) 어떤 팀이든 팀에 소속된 회원

1
2
SELECT m FROM Member m
	WHERE m.team = ANY(SELECT t FROM Team t)

JPQL 타입 표현

  • 문자 : ‘HELLO’, ‘SHE”s’
  • 숫자 : 10L(Long), 10D(Double) ..
  • Boolean : TRUE, FALSE
  • ENUM : jpabook.MemberType.Admin (패키지명 포함)
  • 엔티티 타입 : TYPE(m) = Member (상속 관계에서 사용한다.)

예시

1
2
SELECT m.username, 'HELLO', true From Member m 
	WHERE m.type = jpql.MemberType.ADMIN
1
2
List<Object[]> result = em.createQuery(query)
	.getResultList();

위 쿼리를 자바코드로 뽑아내면

1
2
3
memberA
HELLO
true

위와 같은 결과를 볼 수 있다. 물론 memberA 라는 이름을 가진 Member가 ADMIN Enum 타입인 것이다.

엔티티 타입 예시

1
SELECT i FROM Item i WHERE type(i) = Book

Item과 Book은 상속관계이고 Book은 Dtype인 것이다.

즉 Dtype이 Book인 Item을 select.

조건식

  • 기본 CASE 식
1
2
3
4
5
6
SELECT
	CASE when m.age <= 10 then '학생요금'
		 when m.age >= 60 then '경로요금'
		 else '일반요금'
	end
FROM member m	 
  • 단순 CASE 식
1
2
3
4
5
6
7
SELECT
	CASE t.name
		when '팀A' then '인센티브110%'
		when '팀B' then '인센티브120%'
		else '인센티브105%'
	end
FROM Team t

자바 코드로 본다면 아래와 같다.

1
2
3
4
5
6
7
String query = 
	"select " +
		"case when m.age <= 10 then '학생요금' " +
		"	  when m.age >= 60 then '경로요금' " +
		"     else '일반요금' " +
		"end " +
	"from Member m";

다른 조건식

  • COALESCE : 하나씩 조회해서 null이 아니라면 반환한다.
1
SELECT COALESCE(m.username, '이름 없는 회원') FROM Member m

*null이 아니면 ‘이름 없는 회원’ 이라는 String을 반환하는 것이다.

  • NULLIF : 두 값이 같으면 null 반환, 다르다면 첫 번째 값 반환
1
SELECT NULLIF(m.username, '관리자') FROM Member m

JPQL 기본 함수

  • CONCAT
  • SUBSTRING
  • TRIM
  • LOWER, UPPER
  • LENGTH
  • LOCATE
    • locate(‘de’, ‘abcde’) 라면 abcde에서 de의 위치를 찾아 반환.
  • ABS, SQRT, MOD
  • SIZE, INDEX(JPA 용도)
1
String query = "select concat('a', 'b') from Member m";

사용자 정의 함수 호출

  • 사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록해 사용한다.
    • 사용 전 하이버네이트 방언 추가 필요
1
2
3
4
5
6
public class MyH2Dialect extends H2Dialect {
	
	public MyH2Dialect() {
		registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING);
	}
}
1
select function('group_concat', i.name) from Item i
This post is licensed under CC BY 4.0 by the author.