JPQL도 SQL과 비슷하게 SELECT
, UPDATE
, DELETE
문을 사용할 수 있다. em.persist()
메소드가 있기에 INSERT
문은 없다.
JPQL example
SELECT m FROM Member AS m where m.username = 'Hello
Member
는 클래스 명이 아니라 엔티티 명이다. 엔티티 명은 @Entitiy(name=~~~)
로 지정할 수 있다. 엔티티 명을 지정하지 않으면 클래스 명을 기본값으로 사용한다. 기본값인 클래스 명을 엔티티 명으로 사용하는 것을 추천한다.Member AS m
을 보면 m
이라는 별칭을 주었다. JPQL은 별칭을 필수로 사용해야한다. AS
는 생략가능하다. 하이버네이트는 더 많은 기능을 가진 HQL(Hibernate Query Language)를 제공한다. HQL의 경우 별칭 없이 사용할 수 있다.작성한 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. 순서와 타입이 일치하는 생성자가 필요하다.
페이징 처리용 SQL은 데이터베이스마다 문법이 다른 경우가 많다. JPA는 페이징을 다음 두 API로 추상화 했다.
데이터베이스마다 다른 페이징 처리를 같은 API로 처리할 수 있는 것은 데이베이스 방언(Dialect)덕분이다.
JpaRepository
는 PagingAndSortingRepository
를 상속한다. 페이징 처리를 쉽게 해주는 인터페이스며 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
DISTINCT
를 COUNT
에서 사용할 때 임베디드 타입은 지원하지 않는다.GROUP BY
, HAVING
GROUP BY
는 통계 데이터를 구할 때 특정 그룹 끼리 묶어 준다. HAVING
은 그룹화한 통계 데이터 기준으로 필터링한다.
이런 쿼리들을 보통 리포팅 쿼리나 통계 쿼리라고 한다. 통계 쿼리는 보통 전체 데이터를 기준으로 처리하므로 실시간으로 사용하기엔 부담이 많다. 결과가 아주 많다면 통계 결과만 저장하는 테이블을 별도로 만들어 두고 사용자가 적은 새벽에 통계 쿼리를 실행해서 그 결과를 보관하는 것이 좋다.
ORDER BY
ORDER BY
는 결과를 정렬할 때 사용한다.
자바 ORM 표준 JPA 프로그래밍 10장