JPA의 JPQL 두번째 시간이다.
간단간단하게 빠르게 가자!

TypedQuery, Query

작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 한다. 쿼리 객체는 TypedQuery와 Query가 있는데 반환할 타입이 명확하게 지정 할 수 있으면 TypedQuery 아니면 Query를 선택하면 된다.

TypedQuery<Member> typedQuery = entityManager.createQuery("select m from Member m", Member.class);
System.out.println(typedQuery.getResultList()
      .stream()
      .map(i -> i.toString())
      .collect(joining("\n"))
);

Query query = entityManager.createQuery("select m.name, m.email from Member m");
List<Object[]> result = query.getResultList();
result.stream().forEach(i -> {
  System.out.println(i[0]);
  System.out.println(i[1]);
});

TypedQuery 같은 경우에는 Member.class를 지정해주므로 반환타입이 명확한 반면에 Query는 명확하지 않다.
만약 m.email이 아닌 m.age 라는 컬럼이 있다고 가정하면 age는 Integer로 반환한다. 그럼 타입이 명확하지 않으므로 위와 같이 해야 된다.

파라미터 바인딩

두가지 방법으로 파라미터 바인딩을 할 수 있다. 위치 기반과 이름 기반으로 가능하다.

    TypedQuery<Member> nameTypedQuery = entityManager.createQuery("select m from Member m where m.name = :name", Member.class);
    nameTypedQuery.setParameter("name", "wonwoo");
    nameTypedQuery.getResultList().stream().forEach(System.out::println);

    TypedQuery<Member> positionTypedQuery = entityManager.createQuery("select m from Member m where m.name = ?1", Member.class);
    positionTypedQuery.setParameter(1, "wonwoo");
    positionTypedQuery.getResultList().stream().forEach(System.out::println);

다음과 같이 명시적으로 name을 쓰는 방법과 1인 위치 기반으로 설정 가능하다. 하지만 보통은 위치 기반보다는 이름 기반으로 한다.
파라미터 바인딩을 사용하지 않고 명시적으로 직접 문자를 더해 만들어 넣으면 악의적인 사용자가 SQL 인젝션 공격을 할 수 있으니 반드시 바인딩을 해서 사용하자!

프로젝션

select 절에 조회할 대상을 지정하는 것을 프로젝션이라 한다.

  • 엔티티 프로젝션
    SELECT m FROM Member m //회원
    SELECT m.team FROM Member m //팀

처음에는 회원 그리고 두번째는 팀을 조회 했다. 둘다 엔티티를 프로젝션 대상으로 사용했다. 이렇게 조회한 엔티티는 영속성 컨텍스트에 관리 된다.

  • 임베디드 타입
    JPQL에서 임베디드 타입은 엔티티와 비슷하게 사용한다. 임베디드 타입은 조회의 시작점이 될 수 없다.
    “select a FROM Address a” 임베디드 타입으로 설정 된 Address는 조회의 시작점이 될 수 없다.

“select o.address FROM Order o”
다음과 같이 변경 해야된다.

  • 여러값 조회
    우리는 여러 타입을 조회 하려면 TypedQuery를 사용할 수 없고 Query를 사용해야 된다.
    하지만 매번 캐스티을 해줘야 한다.
    여러 방법이 있으나 흔히 쓰는 DTO로 하겠다.
    우리는 new 연산자를 써서 쉽게 DTO로 변환 할 수 있다.
    TypedQuery<MemberDTO> queryDto = entityManager.createQuery(
      "select new me.wonwoo.exam10.dto.MemberDTO(m.name, m.email) from Member m", MemberDTO.class
    );
    System.out.println(queryDto.getResultList()
      .stream()
      .map(i -> i.toString())
      .collect(joining("\n")));

다음과 같이 말이다. 여기 주의할 점은 실제 생성하기 때문에 name과 email이 있는 생성자가 있어야 한다.

페이징

페이징은 정말 지루하고 반복적인 일이다. JPA는 페이징을 추상화하여 처리 하였다.

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

    TypedQuery<Member> pageQuery = entityManager.createQuery(
      "select m from Member m ORDER BY m.name desc", Member.class
    );
    pageQuery.setFirstResult(1);
    pageQuery.setMaxResults(2);
    System.out.println(pageQuery.getResultList()
      .stream()
      .map(i -> i.toString())
      .collect(joining("\n")));

만약 위와 같이 했다면 시작점은 1이고 총 2개가 나오는 것이다. 그러니 첫번째를 제외하고 2번째과 3번째가 나올 것이다.

집합과 정렬

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

select 
  count(m)
  sum(m.age)
  avg(m.age)
  max(m.age)
  min(m.age)
from 
  Member m

각각의 회원수, 나이의 합, 평균, 최고 나이, 최소 나이 등을 구하는 함수이다.

이외에도 group by, order by 등이 있다. 거의 일반 sql과 비슷하다. 거의 같다고 봐도 될듯하다?

다음시간엔 조인에 대해 알아보자! 좀 더 공부를 하고 해야될듯

출처 : 자바 ORM 표준 JPA 프로그래밍 (김영한)