[JPA] JPQL

yookyungmin·2023년 5월 23일
0

JPQL 특징

  • 테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리이다.
  • SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
  • 대소문자구분
    엔티티와 속성은 대소문자를 구분한다. 예를 들어 Member, username은 대소문
    자를 구분한다. 반면에 SELECT, FROM, AS 같은 JPQL 키워드는 대소문자를 구분
    하지 않는다.
  • 별칭은 필수
    별칭 없이 작성하면 잘못된 문법 이 라는 오
    류가 발생한다.

TypeQuery & Query

  • 반환할 타입을 명확하게 지정할 수 있으면TypeQuery, 않으면 Query 객체를 사용하면 된다
//TypeqQuery
TypeQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
//Query
Query qeury = em.createQuery("SELECT m.username, m.age From Member m");
List resultList = query.getReusltList();
  • 결과 조회
    query.getResultList() : 결과를 예제로 반환한다. 만약 결과가 없으면 빈 컬렉션을 반환.
    query.getSingleReulst() : 결과가 정확히 하나일때 사용한다.
    결과가 없으면 javax.persistence.NoResultException 예외가 발생한다.
    결과과 1 개보다 많으면 javax.persistence.NonUniqueResultException
    예외가 발생한다.
    getSingleResult ()는 결과가 정확히 1 개가 아니면 예외가 발생한다는 점에 주
    의해야 한다
@Entitiy(name="Member")
public class Member{
 	@Column(name = "name")
    private String username;
}
//JPQL사용
String jpql = "select m from member as m where m.username= "kim";
List<Member> resultList = em.createQuery(jpql, Member.class).gtResultList():
회원이름이 kim인 엔티티를 조회한다 m.username은 테이블 컬럼명이 아니라 엔티티 객체의 필드명이다.

파라미터 바인딩

  • JDBC는 위치 기준 파라미터 바인딩만 지원하지만, JPQL은 이름 기준 파라미터 바인딩을 지원한다.
    이름 기준 파라미터는 파라미터를 이름으로 구분하는 방법이다. 이름 기준 파라미터는 앞에 :를 사용한다.
// 이름 기준 파라미터 사용
String usernameParam = "User1";
TypedQuery<Member> query = 
		em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class);
query.setParameter("username", usernameParam);
List<Member> resultList = query.getResultList();

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

List<Member> members = em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class)
  .setParameter(1, usernameParam)
  .getResultList();

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

파라미터 바인딩 사용 이유

  • JPQL을 수정해서 다음 코드처럼 파라미터 바인딩 방식을 사용하지 않고 직접 문자를 더해 만들어 넣으면 악의적인 사용자에 의해 SQL 인젝션 공격을 당할수 있다.

  • 파라미터 바인딩 방식을 사용하면 파라미터의 값이 달라도 같은 쿼리로 인식해서 JPQ는 JPQL을 SQL로 파싱한 결과를 재사용 가능해서 성능이 좋아진다.

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

프로젝션

  • Select 절에서 조회할 대상을 지정하는 것을 프로젝션이라고 한다.
  • 프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입이 있다. 스칼라 타입은 숫자, 문자 등 기본 데이터 타입을 뜻한다.

엔티티 프로젝션

SELECT m FROM Member m //회원
  SELECT m.team FROM Member m //팀

둘 다 엔티티를 프로젝션 대상으로 사용했다.조회한 엔티티는 영속성 컨텍스트에서 관리된다.

임베디드 타입 프로젝션

임베디드 타입은 엔티티와 거의 비슷하게 사용된다.
임베디드 타입은 조회의 시작점이 될 수 없다는 제약이 있다.

 String query = "SELECT a FROM Address a"; 
 잘못된 쿼리
  String query = "SELECT o.address FROM Order o";
 List<Address> addresses = em.createQuery(query, Address.class).getResultList();
  임베디드 타입인 Address를 시작점으로 사용하면 안되기 떄문에 Order 엔티티를 시작점으로 해서 조회하였다.

임베디드 타입은 엔티티 타입이 아닌 값 타입이기에 이렇게 조회 된 경우에는 영속성 컨텍스트에 관리 되지 않는다.

스칼라타입 프로젝션

숫자, 문자, 날짜와 같은 기본 데이터 타입들을 스칼라 타입이라 한다.
전체 회원의 이름을 조회 할땐 다음처럼 쿼리하면 된다.

List<String> usernames = em.createQuery("SELECT username FROM Member m", String.class)
  .getResultList();

중복데이터를 제거하려면 DISTINCT를 사용한다.

SELECT DISTINCT username FROM Member m

다음과 같은 통계 쿼리도 주로 스칼라 타입을 조회한다. 통계 쿼리용 함수들은 뒤에서 설명하겠다.

Double orderAmountAvg = em.createQuery("SELECT AVG(o.orderAmount) FROM Order o", Double.class)
.getSingResult();

여러 값 조회

꼭 필요한 데이터만 조회해야 할때
프로젝션에 여러 값을 선택하면 TypeQuery를 사용할 수 없고 대신에 Query를 사용해야 한다.

 Query query = em.createQuery("SELECT m.username, m.age FROM Member m");
  List resultList = query.getResultList();
  Iterator iterator = resultList.iterator();
  while(iterator.hasNext()){
  Object[] row = (Object[]) iterator.next();
  String username = (String) row[0];
  Integer age = (Integer) row[1];
  }

스칼라 타입 뿐만 아니라 엔티티 타입도 여러값을 함께 조회할 수 있다.

List<Object[]> resultList = em.createQuery("SELECT o.member, o.prodcut, o.orderAmount FROM Order o")
.getResultList();
for(Object[] row:resultList){
Member member = (Member) row[0]; //엔티티
Product product = (Prodcut) row[1]; //엔티티
int OrderAmount = (Integer) row[2];
}

NEW 명렁어

실제 개발자는 UserDTO 처럼 의미있는 객체로 변환해서 사용할 것이다.

TypeQuery<UserDTO> query = em.createQuery("SELECT new jpabook.jqpl(UserDTO(m.username, m.age)
FROM Member m", UserDTO.class);
List<UserDTO> resultList = query.getResultList();

NEW 명령어를 사용한 클래스로 TypeQuery를 사용할 수 있어서 지루한 객체 변환 작업을 줄일 수 있다.
NEW 명령어 사용할 때는 다음 2가지를 주의해야 한다.
1. 패키지 명을 포함한 전체 클래스 명을 입력해야 한다
2. 순서와 타입이 일치하는 생성자가 필요하다.

페이징 API

  • setFirstResult(int startPosition) : 조회 시작위치(0부터 시작된다)
  • setMaxResult(int maxResult) 조회할 데이터수
    TypeQuery<Member> query = em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC", Member.class);
    query.setFirstResult(10);
    query.setMaxResults(20);
    query.getResultList();
    더 최적화 하고 싶다면 JPA가 제공하는 페이징 API가 아닌 네이티브 SQL을 직접 사용해야 한다.

집합과 정렬

집합은 집합 함수와 함계 통계정보를 구할때 사용된다.
예를 들어 다음 코드는 순서대로 회원수, 나이 합, 평균 나이, 최대 나이, 최소 나이를 조회한다.

select
COUNT (m) z //회원수
SUM(m.age), 〃나이 합
AVG(m.age), //평균 나이
MAX (m.age), //최대 나이
MIN (m age) //최소 나이
from Member m

집합 함수의 종류와 그 리턴값을 정리한 내용은 아래와 같다

COUNT : 결과 수를 구한다. 반환 타입은 LONG
MAX, MIN : 최대, 최소 값을 구한다. 문ㅈ, 숫자, 날짜 등에 사용한다.
AVG : 평균값을 구한다. 숫자타입만 사용할 수 있다. 반환 타입은 DOUBLE
SUM : 합을 구한다. 숫자 타입만 사용이 가능하다. 반환 타입 : 정수의 합(LONG), 소수의 합(DOUBLE)

집합 함수 사용 시 참고사항

  • NULL 값은 무시하므로 통계에 잡히지 않는다(D1STエNCT가 정의되어 있어도 무시된다).

  • 만약 값이 없는데 SUM, AVG, MAX, MIN 함수를 사용하면 NULL 값이 된다. 단
    COUNT는 0이 된다.

  • DISTINCT 를 집합 함수 안에 사용해서 중복된 값을 제거하고 나서 집합을 구할
    수 있다.
    예 select COUNT ( DISTINCT m.age ) from Member m

  • DISTINCT를 COUNT에서 사용할 때 임베디드 타입은 지원하지 않는다

    참고 : 자바 ORM 표준 jpa 프로그래밍

0개의 댓글