JPA는 공식적으로 지원하는 객체지향 쿼리 기능이 존재한다.
공식적이진 않지만 알아둬야할 기능
JPQL은 엔티티 객체를 조회하는 객체지향 쿼리다.
JPQL은 SQL을 추상화해 특정 데이터베이스에 의존하지 않는다.
JPQL은 SQL보다 간결하다.
select_문 :: =
select_절
from_절
[where_절]
[groupby_절]
[having_절]
[orderby_절]
update_문 ::= update_절 [where_절]
delete_문 ::= delete_절 [where_절]
select
, from
같은 JPQL 키워드는 구분하지 않는다JPQL을 실행하기 위한 쿼리 객체이다. 쿼리 객체는 TypeQuery
, Query
가 있으나 반환 타입을 명확히 지정할 수 있다면 TypeQuery
를 사용, 없으면 Query
를 사용한다.
ex)
TypeQuery<Member> query =
em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
Query query =
em.createQuery("SELECT m FROM Member m");
List resultList = query.getResultList();
query.getResultList()
query.getSingleResult()
NoResultException
발생한다NoUniqueResultException
발생한다.쿼리 변수 앞에 : 를 넣어 사용한다
String usernameParam = "User"
TypeQuery<Member> query =
em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class);
쿼리 변수 앞에 ? 를 넣어 사용한다. 위치 값은 1부터 시작한다.
List<Member> members =
em.createQuery("SELECT m FROM Member m where m.username
= ?1", Member.class)
.setParameter(1, usernameParam)
.getResultList();
SELECT 절에 조회할 대상을 지정하는 것을 프로젝션이라 칭한다.
프로젝션 대상은 엔티티, 엠비디드 타입, 스칼라 타입이 있다.
SELECT m FROM Member m // 회원
SELECT m.team FROM Member m // 팀
컬럼을 나열해서 조회해야하는 것이 아닌 원하는 객체를 바로 조회하는 것이 엔티티 프로젝션이다.
이 조회한 엔티티는 영속성 컨텍스트에서 관리된다.
String query = "SELECT a FROM Order o";
List<Address> address = em.createQuery(query, Address.class)
.getResultList();
임베디드 타입은 조회의 시작점이 될 수 없다.
임베디드 타입은 엔티티 타입이 아닌 값 타입이다. 따라서 이렇게 직접 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.
String query = "SELECT a FROM Order o";
List<String> username = em.createQuery("SELECT username FROM Member m", String.class)
.getResultList();
숫자, 문자, 날짜같은 기본 데이터 타입들을 스칼라 타입이라 한다.
Query query =
em.createQuery("SELECT m.username, m.age FROM Member m");
List resultList = query.getResultList();
// or
List<Object[]> resultList = query.getResultList();
꼭 필요한 데이터들만 선택해 조회할 경우에 사용한다. 프로젝션에 여러 값을 선택하면 TypeQuery를 사용하지 못하고 Query를 사용해야한다.
Query query =
em.createQuery("SELECT o.member. o.product, o.orderAmount FROM Order o");
List<Object[]> resultList = query.getResultList();
for (Object[] row : resultList) {
Member member = (Member) row[o];
Product product = (product) row[1];
int orderAmount = (Integer)row[2];
}
이 때 조회한 엔티티는 영속성 컨텍스트에서 관리된다.
TypeQuery<UserDTO> query =
em.createQuery("SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m", UserDTO.class);
List resultList = query.getResultList();
// or
List<UserDTO> resultList = query.getResultList();
NEW 명령어를 사용해반환 클래스를 지정받을 수 있다.
주의점
JPA는 페이징을 두 API로 추상화했다.
setFirstResult(int startPosition)
: 조회 시작 위치(0부터 시작한다)setMaxResults(int maxResult)
: 조회할 데이터 수TypeQuery<UserDTO> query =
em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC", Member.class);
query.setFirstResult(10);
query.setMaxResults(20);
query.getResultList();
집합은 집합함수와 함께 통계 정보를 구할 때 사용한다.
함수 | 설명 |
---|---|
COUNT | 결과 수를 구한다. 반환 타입 : Long |
MAX, MIN | 최대, 최소 값을 구한다. 문자, 숫자, 날짜 등에 사용한다. |
AVG | 평균값을 구한다. 숫자타입만 사용할 수 있다. 반환 타입: Double |
SUM | 합을 구한다. 숫자타입만 사용할 수 있다. 반환 타입: 정수합 Long, 소수합: Double, BigInteger합 : BigInteger, BigDecimal합 : BigDecimal |
내부조인은 inner join을 사용한다.
String teamName = "팀A";
String query = "SELECT m FROM Member m (INNER) JOIN m.team t WHERE t.name = :teamName";
List<Member> members = em.createQuery(query, Member.class)
.setParameter("teamName", teamName)
.getResultList();
외부 조인은 left (outer) join을 사용한다.
SELECT m
FROM Member m LEFT [OUTER] JOIN m.team t
일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하는 것을 컬렉션 조인이라 한다.
SELECT t, m
FROM Team t LEFT JOIN t.member m
t LEFT JOIN t.member m
은 팀과 멤버를 컬렉션 값 연관 필드로 외부조인했다.WHERE절을 사용해 세타 조인이 가능하다. 세타 조인은 내부 조인만 지원한다.
//JPQL
select count(m) from Member m, Team t
where m.user = t.name
JPA 2.1부터 조인 시 ON 절을 지원한다. ON절로 인해 조인 대상을 필터링 조인할 수 있다.
내부조인의 ON절은 WHERE절과 결과가 같아 보통 외부 조인에서만 사용한다.
//JPQL
select m, t from Member m left join m.team t on t.name = 'A'
where m.user = t.name
연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능으로 join fetch로 사용할 수 있다.
select m from Member m join fetch m.team
페치 조인을 통해 지연 로딩되어있는 연관 엔티티들도 한번에 가져올 수 있다.
select t from Team t join fetch t.members
where t.name "팀A"
페치 조인을 통해 지연 로딩되어있는 멤버 엔티티들도 함께 조회된다.
JPQL의 DISTINCT 명령어는 SQL에 추가하고 또 애플리케이션에서 한 번 더 중복을 제거한다.
경로 표현식이란 .(점)을 찍어 객체 그래프를 탐색하는 것이다.
select m.username from Team t join t.members m
select t.members.size from Team t
서브쿼리는 WHERE, HAVING 절에서만 사용가능하다.
select m from Member m
where (select count(o) from Order o where m = o.member) > 0
컬렉션 식은 컬렉션에서만 사용할 수 있고 컬렉션은 컬렉션 식밖에 사용할 수 없다.
JQPL로 부모 엔티티를 조회하면 자식 엔티티도 함께 조회된다.
이를 선택하려면 TYPE을 사용한다
select i from Item i
where type(i) IN (BOOK, MOVIE)
JPA 2.1부터 자바의 타입 캐스팅과 비슷한 기능이 생겼다. 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다.
JPA 표준은 FROM, WHERE절에서 사용 가능하지만 하이버네이트에서는 SELECT 절에서도 사용가능하다.
select i from Item i where treat(i as Book).author = 'kim'
참고 도서