본문 바로가기

Spring/JPA & Hibernate

JPQL(Java Persistence Query Language) - 1

반응형

JPQL(Java Persistence Query Language)

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

JPQL 문법

  • select u from User as u where u.age > 10
    엔티티와 속성은 대소문자 구분한다.
    User 는 엔티티 age는 속성
  • JPQL 키워드는 대소문자 구분하지 않아도 된다. (SELECT, FROM, where)
    테이블의 이름이 아닌 엔티티 이름 사용한다.
    tbUser 가 아닌 User.
  • 문법상 별칭은 필수이다. (as는 생략가능)

TypeQuery, Query

  • TypeQuery : 반환 타입이 명확할 때 사용
  • Query : 반환 타입이 명확하지 않을 때 사용

User user = new User();
user.setName("coco");
user.setAge(10);

entityManager.persist(user);

TypedQuery<User> userTypedQuery = entityManager.createQuery("select u from User as u", User.class);
System.out.println(userTypedQuery.getResultList().get(0).getClass());

Query query = entityManager.createQuery("select u from User as u");
System.out.println(query.getResultList().get(0).getClass());

TypedQuery 시 지정해준 클래스 타입으로 가져온 것을 확인할 수 있다.

물론, Query 시에도 맵핑 정보를 가지고 쿼리 결과의 클래스 타입인지 알 수 있다.

 

Query는 다음과 같이 가져오는 속성의 타입을 지정할 수 없을 경우에 사용할 수 있다.


Query query2 = entityManager.createQuery("select u.id, u.name from User as u");
System.out.println(query2.getResultList().get(0).getClass());

 

결과 조회 API

  • query.getResultList() : 결과가 하나 이상일 때, 리스트 반환
  • query.getSingleResult() : 결과가 정확히 하나단일 객체 반환

 

query.getSingleResult() 사용 예시


ypedQuery<User> userTypedQuery = entityManager.createQuery("select u from User as u where u.id = 1", User.class);
System.out.println(userTypedQuery.getSingleResult().toString());

query.getResultList() 의 경우 결과가 없으면 빈 리스트 반환한다.

그러나, query.getSingleResult() 의 경우 결과가 없으면 javax.persistence.NoResultException

결과가 둘 이상이면 javax.persistence.NonUniqueResultException 를 발생시킨다.

 


TypedQuery<User> userTypedQuery2 = entityManager.createQuery("select u from User as u where u.id = 2", User.class);
try {
	System.out.println(userTypedQuery2.getSingleResult().toString());
} catch (NoResultException e) {
	System.out.println("result is null");
}
Spring Data JPA 에서는 결과가 없으면 try-catch 로 optional 또는 결과가 없으면 null 을 반화 해준다.

파라미터 바인딩


User coco = entityManager.createQuery("select u from User as u where u.name = :userName", User.class)
			.setParameter("userName", "coco")
			.getSingleResult();

System.out.println(coco.toString());

프로젝션

프로젝션이란 SELECT 절에 조회할 대상을 지정할 수 있는 기능이다.

프로젝션으로 데이터를 가져오면 가져온 모든 엔티티는 영속성 컨택스트에서 관리된다.

 

프로젝션 대상

  • 엔티티
    SELECT u FROM User u
    SELECT u.team FROM User u 
  • 임베디드 타입
    SELECT u.address FROM User u
  • 스칼라 타입(숫자문자등 기본 데이터 타
    SELECT u.name, u.age FROM User u

🔎 엔티티 프로젝션


Team resultTeam = entityManager.createQuery("select u.team from User as u", Team.class).getSingleResult();
System.out.println(resultTeam.toString());

User 의 Team 가져오기

연관관계의 엔티티를 가져올 경우, 내부적으로 JOIN 쿼리가 발생한다. (묵시적 조인)

쿼리에서 JOIN 쿼리가 발생할 것을 확인할 수 있도록, 아래와 같이 쓰는 것이 좋다. (명시적 조인)


Team resultTeam = entityManager.createQuery("select t from User as u join u.team t", Team.class).getSingleResult();

 

🔎 엔티티 프로젝션


Address resultAddress = entityManager.createQuery("select u.address from User as u", Address.class).getSingleResult();
System.out.println(resultAddress.toString());

User Address 가져오기

🔎 스칼라 타입 프로젝션

 

Object[] 로 타입 변환 조회


List<Object[]> resultList = entityManager.createQuery("select distinct u.name, u.age from User as u").getResultList();
resultList.stream().forEach(o -> System.out.println("name = " + o[0] + " age = " + o[1]));

new 명령어로 조회


List<UserDTO> resultList = entityManager.createQuery("select new com.juns.playground.dto.UserDTO(u.name, u.age) from User as u", UserDTO.class).getResultList();
resultList.stream().forEach(dto -> System.out.println("name = " + dto.getName() + " age = " + dto.getAge()));

  • 단순 값을 DTO로 바로 조회
할 수 있다.
    select new com.juns.playground.dto.UserDTO(u.name, u.age) from User as u

  • 패키지명을 포함한 전체 클래스명 입력해야 한다.
    QueryDSL 에서 극복 가능

  • 순서와 타입이 일치하는 생성자 필요하다.

페이징 API

JPA는 페이징을 다음 두 API로 추상화되어 있어, 두 API 만 설정해주면 된다.

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

List<User> pagingList = entityManager.createQuery("select u from User as u order by u.id desc ", User.class)
	.setFirstResult(0)
	.setMaxResults(10)
	.getResultList();
pagingList.stream().forEach(resultUser -> System.out.println(resultUser.toString()));                                                    .setFirstResult(0)
 

 

조인

  • 내부 조인
    SELECT u FROM User u [INNER] JOIN u.team t
  • 외부 조인
    SELECT u FROM User u LEFT [OUTER] JOIN u.team t
  • 세타 조인 (cross join)
    select count(u) from User u, Team t where u.username = t.name

✔︎JPA 2.1부터 ON절을 활용한 조인을 지원한다.

  • 조인 대상을 필터링할 수 있고
  • 연관관계 없는 엔티티도 외부 조인이 가능하다. (하이버네이트 5.1부터)

조인 대상을 필터링


List<User> joinQueryList = entityManager.createQuery("select u from User as u left join u.team as t on t.name = 'juns team'", User.class).getResultList();
joinQueryList.stream().forEach(resultUser -> System.out.println(resultUser.toString()));

 

서브 쿼리

  • 나이가 평균보다 많은 회원
    select u from User uwhere u.age > (select avg(u2.age) from User u2)
    => 새로운 u2를 정의하여 성능 개선
  • 한 건이라도 주문한 고객
    select u from User uwhere (select count(o) from Order o where u = o.user) > 0

서브 쿼리 지원 함수

✔︎ [NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참 {ALL | ANY | SOME} (subquery)

  • EXISTS
    A 소속인 회원
    select u from User uwhere exists (select t from u.team t where t.name = ‘juns team')
  • ALL 모두 만족하면 참
    전체 상품 각각의 재고보다 주문량이 많은 주문들
    select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)
  • ANY, SOME: 같은 의미, 조건을 하나라도 만족하면 참
    어떤 팀이든 팀에 소속된 회원 
    select u from User u where u.team = ANY (select t from Team t)

✔︎ [NOT] IN (subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참

 

JPA 서브 쿼리 한계

  • JPAWHERE, HAVING 절에서만 서브 쿼리 사용 가능
  • SELECT 절도 가능(하이버네이트에서 지원)
  • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
    => 조인으로 풀 수 있으면 풀어서 해결

JPQL 타입 표현

  • 문자: 'JPA'
  • Boolean: TRUE, FALSE
  • 숫자: 10L(Long), 10D(Double), 10F(Float)
  • ENUM의 경우 패키지명 포함하여 엔티티 타입을 지정하거나 파라미터 표현식으로
  • 엔티티 타입 (상속 관계에서 사용)
    TYPE(m) = Member
    세팅한 DiscriminatorValue (DTYPE)로 검색

List<User> res = entityManager.createQuery("select u from User as u where u.type = com.juns.playground.model.enumeration.UserType.Admin", User.class).getResultList();

OR


List<User> res = entityManager.createQuery("select u from User as u where u.type = :type", User.class)
	.setParameter("type", UserType.Admin)
	.getResultList();

SQL과 문법이 같은 식

  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <> BETWEEN, LIKE, IS NULL

조건식 - CASE

  • 기본 CASE
    select
        case when m.age <= 10 then '학생요금'
                when m.age >= 60 then '경로요금'
                else '일반요금'
         end
    from Member m
  • 단순 CASE
    select
        case t.name
            when 'A' then '인센티브110%'
            when 'B' then '인센티브120%'
            else '인센티브105%'
        end
    from Team t
  • COALESCE: 하나씩 조회해서 null이 아니면 반환
    사용자 이름이 없으면 이름 없는 회원을 반환
    select coalesce(m.username,'이름 없는 회원') from Member m
  • NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
    사용자 이름이 관리자null을 반환하고 나머지는 본인의 이름을 반환
    select NULLIF(m.username, '관리자') from Member m

JPQL 기본 함수

  • CONCAT
  • SUBSTRING
  • TRIM
  • LOWER, UPPER
  • LENGTH
  • LOCATE
  • ABS, SQRT, MOD
  • SIZE, INDEX(JPA 용도)

사용자 정의 함수 호출

  • 하이버네이트는 사용전 방언에 추가해야 한다.
    사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.
    select function('group_concat', i.name) from Item i

 

반응형

'Spring > JPA & Hibernate' 카테고리의 다른 글

Spring Data JPA - H2 연동  (0) 2020.12.24
JPQL(Java Persistence Query Language) - 2  (0) 2020.12.22
JPA 쿼리  (0) 2020.12.20
연관관계 관리 (즉시 로딩과 지연 로딩)  (0) 2020.12.18
Transaction  (0) 2020.12.15