JPQL 기본 문법

Gunjoo Ahn·2023년 8월 24일
0
post-custom-banner

JPQL 특징

  • JPQL은 객체지향 쿼리 언어이다. 따라서 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
  • JPQL은 SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
  • JPQL은 결국 SQL로 변환된다.

기본 문법과 쿼리 API

JPQL도 SQL과 비슷하게 SELECT, UPDATE, DELETE 문을 사용할 수 있다. em.persist() 메소드가 있기에 INSERT문은 없다.

JPQL example

SELECT m FROM Member AS m where m.username = 'Hello

  1. 엔티티와 속성은 대소문자 구분을 한다. 반면 JPQL 키워드는 대소문자를 구분하지 않는다.
  2. 또 JPQL에서 사용한 Member는 클래스 명이 아니라 엔티티 명이다. 엔티티 명은 @Entitiy(name=~~~)로 지정할 수 있다. 엔티티 명을 지정하지 않으면 클래스 명을 기본값으로 사용한다. 기본값인 클래스 명을 엔티티 명으로 사용하는 것을 추천한다.
  3. Member AS m을 보면 m이라는 별칭을 주었다. JPQL은 별칭을 필수로 사용해야한다. AS는 생략가능하다. 하이버네이트는 더 많은 기능을 가진 HQL(Hibernate Query Language)를 제공한다. HQL의 경우 별칭 없이 사용할 수 있다.

TypedQuery, Query

작성한 JPQL을 실행하려면 쿼리 객체를 만들어야한다. 반환할 타입을 명확하게 지정할 수 있으면 TypedQuery 객체를 사용하고, 반환 타입을 명확하게 지정할 수 없으면 Query 객체를 사용하면 된다.

결과 조회

  • query.getResultList() : 결과를 리스트로 반환한다. 만약 겨로가가 없으면 빈 컬렉션을 반환한다
  • query.getSingleResult() : 결과가 정확히 하나일 때 사용한다. 결과가 없으면 javax.persistence.NoResultException 예외가 발생한다. 결과가 1개보다 많으면 javax.persistence.NonUniqueResultException 예외가 발생한다.

파라미터 바인딩

JDBC는 위치 기준 파라미터 바인딩만 지원하지만 JPQL은 이름 기준 파라미터 바인딩도 지원한다.

이름 기준 파라미터

이름 기준 파라미터(Named parameters)는 파라미터를 이름으로 구분하는 방법이다. 이름 기준 파라미터 앞에 :을 사용한다.

String usernameParam = "User1";
// :username 부분이 바인드 파라미터이다
List<Member> resultList = 
	em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class)
		.setParameter("username", usernameParam)
		.getResultList();

위치 기준 파라미터

위치 기준 파라미터(Positional parameters)를 사용하려면 ? 다음에 위치 값을 주면 된다. 위치 값은 1부터 시작한다.

위치 기준 파라미터 방식보다는 이름 기준 파라미터 바인딩 방식을 사용하는 것이 더 명확하다.

String usernameParam = "User1";
// :username 부분이 바인드 파라미터이다
List<Member> resultList = 
	em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class)
		.setParameter(1, usernameParam)
		.getResultList();

"select m from Member m where m.username= '" + usernameParam + "'"

파라미터 바인딩 방식을 사용하지 않고 위와 같이 직접 문자를 더해 만들어 넣으면 악의적인 사용자에 의해 SQL 인젝션 공격을 당할 수 있다.

또한 성능 이슈도 있는데 파라미터 바인딩 방식을 사용하면 파라미터의 값이 달라도 같은 쿼리로 인식해서 JPA는 JPQL을 SQL로 파싱한 결과를 재사용할 수 있다. JPA 뿐만 아니라 데이터베이스도 SQL을 캐싱하기도 한다. 결과적으로 애플리케이션과 데이터베이스 모두 해당 쿼리의 파싱 결과를 재사용할 수 있는데, 만약 문자를 더해 만들어 넣으면 이와 같은 성능 이점을 포기하게 된다.

따라서 파라미터 바인딩 방식은 선택이 아닌 필수다.

프로젝션

SELECT 절에 조회할 대상을 지정하는 것을 프로젝션(projection)이라고 하고 SELECT {프로젝션 대상} FROM 으로 대상을 선택한다. 프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입이 있다.

엔티티 타입을 대상으로 할 경우 조회한 엔티티는 영속성 컨텍스트에서 관리된다. 임베디드 타입을 대상으로 프로젝션할 경우 조회의 시작점이 될 수 없다는 제약이 있다. 결국 값 타입이기에 테이블과 매핑되는 엔티티만 조회의 시작점이 될 수 있다고 생각하면 편하다. 추가로 엔티티가 아닌 값이기에 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.

NEW 명령어

NEW명령어를 사용하여 반환된 값들을 기반으로 의미있는 새로운 객체를 반환받을 수 있다.

TypedQuery<UserDTO> query = em.createQuery("SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m", UserDTO.class);

List<UserDTO> resultList = query.getResultList();

SELECT 다음에 NEW 명령어를 사용하면 반환받을 클래스를 지정할 수 있는데 이 클래스의 생성자에 JPQL 조회 결과를 넘겨줄 수 있다. 그리고 NEW 명령어를 사용한 클래스로 TypedQuery를 사용할 수 있어 지루한 객체 변환 작업을 줄일 수 있다.

NEW 명령어를 사용할 때는 다음 두 가지를 주의해야한다.
1. 패키지 명을 포함한 전체 클래스 명을 입력해야 한다.
2. 순서와 타입이 일치하는 생성자가 필요하다.

페이징 API

페이징 처리용 SQL은 데이터베이스마다 문법이 다른 경우가 많다. JPA는 페이징을 다음 두 API로 추상화 했다.

  • setFirstResult(int startPosition): 조회 시작 위치 (0 부터 시작한다)
  • setMaxResults(int maxResult): 조회할 데이터 수

데이터베이스마다 다른 페이징 처리를 같은 API로 처리할 수 있는 것은 데이베이스 방언(Dialect)덕분이다.

Pageable

JpaRepositoryPagingAndSortingRepository를 상속한다. 페이징 처리를 쉽게 해주는 인터페이스며 JPA Spring Docs 링크를 통하여 자세한 내용을 확인할 수 있다. Pageable 인터페이스가 페이지네이션 정보 인터페이스이다.

집합과 정렬

집합은 집합 함수와 함께 통계 정보를 구할 때 사용한다.

함수설명
COUNT결과 수를 구한다. 반환 타입은 Long이다.
MIN, MAX최대, 최소 값을 구한다. 문자, 숫자, 날짜 등에 사용한다.
AVG평균 값을 구한다. 숫자 타입만 가능하며, 반환 타입은 Double이다.
SUM합을 구한다. 숫자 타입만 사용할 수 있으며, 반환 타입은 정수합: Long, 소수합: Double, BigInteger합: BigInteger, BigDecimal합:BigDecimal
  • NULL값은 무시하므로 통계에 잡히지 않는다
  • 값이 없는데 집합 함수를 사용하면 COUNT의 경우 0이며 나머지는 NULL 값이 된다.
  • DISTINCT를 집합 함수안에 사용해서 중복된 값을 제거하고 나서 집합을 구할 수 있다. select COUNT( DISTINCT m.age) from Member m
  • DISTINCTCOUNT에서 사용할 때 임베디드 타입은 지원하지 않는다.

GROUP BY, HAVING

GROUP BY는 통계 데이터를 구할 때 특정 그룹 끼리 묶어 준다. HAVING은 그룹화한 통계 데이터 기준으로 필터링한다.

이런 쿼리들을 보통 리포팅 쿼리통계 쿼리라고 한다. 통계 쿼리는 보통 전체 데이터를 기준으로 처리하므로 실시간으로 사용하기엔 부담이 많다. 결과가 아주 많다면 통계 결과만 저장하는 테이블을 별도로 만들어 두고 사용자가 적은 새벽에 통계 쿼리를 실행해서 그 결과를 보관하는 것이 좋다.

ORDER BY

ORDER BY는 결과를 정렬할 때 사용한다.

  • ASC: 오름차순 (기본 값)
  • DESC: 내림차순

Reference

자바 ORM 표준 JPA 프로그래밍 10장

profile
Backend Developer
post-custom-banner

0개의 댓글