JPA - 객체지향 쿼리언어(2)

DevSeoRex·2022년 12월 6일
0
post-thumbnail
post-custom-banner

프로젝션 - projection

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

엔티티 프로젝션

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

위의 쿼리문을 보면 둘 다 엔티티를 프로젝션 대상으로 사용했다.
이렇게 조회한 엔티티는 영속성 컨텍스트에서 관리된다.

임베디드 타입 프로젝션

  • JPQL에서 임베디드 타입은 엔티티와 거의 비슷하게 사용된다.
  • 임베디드 타입은 조회의 시작점이 될 수 없다는 제약이 있어 주의해야 한다.
String query = "SELECT a FROM Address a";

위의 쿼리는 잘못된 쿼리이다. 왜냐하면 임베디드 타입인 Address를 조회의 시작점으로 사용했기 때문이다.
위의 쿼리를 올바르게 고치면 아래와 같은 쿼리로 변경하게 된다.

String query = "SELECT o.address FROM Order o";
List<Address> address = em.createQuery(query, Address.class)
							.getResultList();
  • 임베디드 타입은 엔티티 타입이 아닌 값 타입이므로 이렇게 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.

스칼라 타입 프로젝션

  • 숫자, 문자, 날짜와 같은 기본 데이터 타입들을 스칼라 타입이라 한다.

전체 회원의 이름을 조회하려면 아래와 같이 쿼리하면 될 것이다.

// 전체 회원의 이름을 조회하는 예제
List<String> usernames = 
		em.createQuery("SELECT username FROM Member m", String.class)
        .getResultList();

중복 데이터를 제거하려면 SELECT절 바로 뒤에 DISTINCT를 사용하면 된다.

통계 쿼리도 주로 스칼라 타입으로 조회한다.

// 스칼라 타입을 사용한 통계쿼리 예제
Double orderAmountAvg = 
		em.createQuery("SELECT AVG(o.orderAmount) FROM Order o", Double.class)
        .getSingleResult();

여러 값 조회

  • 엔티티를 대상으로 조회하면 편리하겠지만, 꼭 필요한 데이터들만 선택해서 조회해야 할 경우도 있다.
  • 프로젝션에 여러 값을 선택하면 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];
}
  • 스칼라 타입뿐만 아니라 엔티티 타입도 여러 값을 함께 조회할 수 있다.
// 여러 프로젝션 엔티티 타입 조회 예제

// 제네릭에 Object[]를 사용하면 조금 더 간결하게 개발할 수 있다.
List<Object[]> resultList = 
		em.createQuery("SELECT o.member, o.product, o.orderAmount FROM Order o")
        .getResultList();
        
for(Object[] row : resultList) {
	Member member = (Member) row[0];			// 엔티티
    Product product = (Product) row[1];			// 엔티티
    int orderAmount = (Integer)	row[2];			// 스칼라
}	

💡 이렇게 조회한 엔티티는 영속성 컨텍스트에서 관리된다.

NEW 명령어

  • 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();

NEW 명령어를 사용할 때는 아래 2가지 사항에 유의해야 한다.

  • 패키지 명을 포함한 전체 클래스 명을 입력해야 한다.
  • 순서와 타입이 일치하는 생성자가 필요하다.
  • 데이터베이스마다 다른 페이징 처리를 같은 API로 처리할 수 있는 편리함을 제공한다(데이터 베이스 방언 사용)

페이징 API

JPA는 페이징을 아래의 두 API로 추상화 했다.

  • setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작된다)
  • setMaxResult(int maxResult) : 조회할 데이터 수
 // 페이징 API 사용 예제
 List<Member> resultList = 
 				em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC")
                .setFirstResult(10)
                .setMaxResult(20)
                .getResultList();

💡 페이징 SQL을 더 최적화 하고 싶다면 JPA가 제공하는 페이징 API가 아닌 네이티브 SQL을 사용해야 한다.

집합과 정렬

  • 집합은 집합함수와 함께 통계 정보를 구할 때 사용한다.
// 순서대로 회원수, 나이 합, 평균 나이, 최대 나이, 최소 나이를 구하는 SQL
SELECT
	COUNT(m),		// 회원 수
    SUM(m.age),		// 나이 합
    AVG(m.age),		// 평균 나이
    MAX(m.age), 	// 최대 나이
    MIN(m.age)		// 최소 나이
FROM Member m

집합 함수

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

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

    💡 SUM은 BigInteger를 합하면 BigInteger, BigDecimal을 합하면 BigDecimal을 반환한다.

집합 함수 사용시 주의할 점

  • NULL 값은 무시하므로 통계에 잡히지 않는다(DISTINCT 키워드를 사용해도 무시된다)
  • 값이 없는데 SUM,AVG,MAX,MIN 함수를 사용하면 NULL 값이 된다. COUNT는 0이 된다.
  • DISTINCT를 집합 함수 안에 사용할 경우, 중복된 값을 제거하고 나서 집합을 구할 수 있다.
  • DISTINCT를 COUNT에서 사용할 때 임베디드 타입은 지원하지 않는다.

출처 : 자바 ORM 표준 JPA 프로그래밍(에이콘, 김영한 저)

post-custom-banner

0개의 댓글