computer_study

[JPA]10. 객체지향 쿼리 언어 본문

스터디/자바 ORM표준 JPA 프로그래밍

[JPA]10. 객체지향 쿼리 언어

knowable 2022. 9. 5. 11:45

1. 객체지향 쿼리 소개

  • 테이블이 아닌 객체를 대상으로 실행
  • JPA가 지원하는 기능
    • JPQL
      • 엔티티 객체를 조회하는 객체지향 쿼리
      • 문법은 SQL과 비슷하나 SQL보다 간결하다
      • SQL을 추상화 해서 특정 데이터베이스 SQL에 의존하지 않는다.
      • JPQL은 결국 SQL로 변환된다.
    • Criteria 쿼리
      • Criteria는 JPQL을 생성하는 빌더 클래스
      • 프로그래밍 코드로 JPQL을 작성할 수 있다.
      • 컴파일 시점에 오류를 발견할 수 있다.
      • IDE를 사용하면 코드 자동완성을 지원한다.
      • 동적 쿼리를 작성하기 편하다.
      • 복잡하고 장황하여 불편하고, 코드가 한 눈에 들어오지 않는다는 단점이 있다.
    • 네이티브 SQL
      • SQL을 직접 사용할 수 있는 기능
      • 특정 데이터베이스만 사용하는 기능이나 힌트들을 사용할 때 사용.
      • 데이터베이스를 변경하면 네이티브 SQL도 수정해야 한다.
    • QueryDSL
      • JPQL빌더 역할을 한다.
      • 코드 기반이면서 단순하고 사용하기 쉽다.
      • 어노테이션 프로세서를 사용해서 쿼리 전용 클래스를 만들어야 한다.
    • JDBC 직접 사용 (MyBatis같은 SQL매퍼 프레임워크 사용)
      • JPA는 JDBC커넥션을 획득하는 API를 제공하지 않기에 JPA 구현체가 제공하는 방법을 사용해야 한다.
      • JDBC나 마이바티스를 JPA와 함께 사용하면 영속성 컨텍스트를 적절한 시점에 강제로 플러시 해야한다. (JDBC나 마이바티스 사용 시 DB에 우회해서 접근하게 되는데, 이는 JPA가 인식할 수 없으므로 플러시를 통해 데이터베이스와 영속성 컨텍스트를 동기화)

2. JPQL

2.1 기본 문법과 쿼리 API

SELECT, UPDATE, DELETE문을 사용할 수 있고, 엔티티 저장 시 EntityManager.persist()메소드를 사용하므로 INSERT문은 없다.

  • SELECT
    • 엔티티와 속성은 대소문자를 구분(SELECT, FROM같은 JPQL키워드는 구분 x)
    • 클래스 명은 엔티티 명으로 사용하는 것을 추천
    • 별칭을 필수로 사용해야 한다 (Member AS m 아니면 Member m)
  • TypeQuery, Query
    • 작성한 JPQL을 실행하기 위한 쿼리 객체
    • 반환 타입이 명확하다면 TypeQuery객체
    • 명확하지 않다면 Query 객체
  • 결과 조회
    • 메소드 호출로 쿼리를 실행해서 결과를 조회할 수 있다.
    • query.getResultList() : 결과를 예제로 반환. 없으면 빈 컬렉션 반환
    • query.getSingleResult() : 결과가 정확히 하나일 때 사용

 

2.2 파라미터 바인딩

  • 이름 기준 파라미터
    • 파라미터를 이름으로 구분
    • em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class) 파라미터 정의
    • query.setParameter("username", usernameParam) 으로 파라미터 바인딩
  • 위치 기준 파라미터
    • em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class).setParameter(1, usernameParam)
  • 위치 기준보단 이름 기준을 사용하는 것이 더 명확 

 

2.3 프로젝션

SELECT절에 조회할 대상을 지정하는 것 (SELECT [프로젝션 대상] FROM)

  • 엔티티 프로젝션
    • SELECT m FROM Member m 같이 원하는 객체를 바로 조회
    • 컬럼을 나열해서 조회하지 않아도 된다.
    • 조회한 엔티티는 영속성 컨텍스트에서 관리
  • 임베디드 타입 프로젝션
    • 엔티티와 거의 비슷하게 사용된다.
    • 조회의 시작이 될 수 없기에 엔티티를 우선 조회 후 임베디드 타입을 조회할 수 있다.
    • Order가 엔티티 Address(city, street, zipcode)가 임베디드 타입이라면 order.city 이런식으로 조회해야
    • 직접 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.
  • 스칼라 타입 프로젝션
    • 숫자, 문자, 날짜와 같은 기본 데이터 타입들
  • 여러 값 조회
    • 필요한 데이터들만 선택해서 조회할 때 Query를 사용해서 가능하다.
    • em.createQuery("SELECT m.username, m.age FROM Member m")
  • NEW 명령어
    • 반환받을 클래스를 지정해서 생성자에 JPQL 조회 결과를 넘겨줄 수 있다. (객체 변환 작업을 줄일 수 있다.)
    • em.createQuery("SELECT new jpabook.jpql.UserDTO(m.username, m.age)
      FROM Member m", UserDTO.class)
    • 주의사항
      • 패키지 명을 포함한 전체 클래스 명을 입력해야 한다.
      • 순서와 타입이 일치하는 생성자가 필요하다.

2.4 페이징 API

페이징을 두 API로 추상화해서, 데이터베이스마다 다른 페이징 처리를 같은 API들로 처리할 수 있도록 하였다(Dialect 사용)

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

 

2.5 집합과 정렬

통계 정보를 구할 때 사용. SQL과 같음

  • 집합함수
    • COUNT
    • MAX, MIN
    • AVG
    • SUM
  • GROUP BY, HAVING
  • ORDER BY

 

2.6 JPQL 조인

SQL조인과 같은 기능. 문법만 살짝 다르다

조인은 스킵

2.8 경로 표현식

"." 을 찍어서 객체 그래프를 탐색할 수 있다.

  • 상태 필드
    • 단순히 값을 저장하기 위한 필드
    • 경로 탐색의 끝으로 더는 탐색할 수 없다.
  • 연관 필드
    • 연관관계를 위한 필드
    • 단일 값 연관 필드
      • 묵시적으로 내부 조인이 일어나고 단일 값 연관 경로는 계속 탐색할 수 있다.
    • 컬렉션 값 연관 필드
      • 묵시적으로 내부 조인이 일어나고 더는 탐색할 수 없다.
      • FROM절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색할 수 있다.
  • 주의사항
    • 항상 내부 조인이다.
    • 묵시적 조인이 사용되므로 sql from절에도 영향을 준다. (명시적 조인을 사용하는 것이 좋다.)

 

2.9 서브쿼리

WHERE, HAVING절에서만 사용할 수 있고 SELECT, FROM절에서는 사용할 수 없다.

 

2.10 조건식

2.11 다형성 쿼리

JPQL로 부모 엔티티를 조회하면 그 자식 엔티티도 함께 조회된다.

2.12 사용자 정의 함수 호출(JPA 2.1)

JPA 2.1 부턴 사용자 정의 함수를 호출할 수 있다.

방언클래스를 상속해서 구현 후 방언을 등록해두어 사용이 가능하다.

 

2.14 엔티티 직접 사용

JPQL에서 엔티티를 직접 사용하면 SQL로 변환될 때 엔티티의 기본 키를 사용하게 된다.

select count (m.id) from Member m
select count (m) from Member m
// 실행되는 SQL은 두 개가 같다

 

2.15 Named쿼리: 정적 쿼리

미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용할 수 있게 하는 것이 정적 쿼리

 

cf) 동적쿼리: em.createQuery("select ..")처럼 JPQL을 문자로 완성해서 직접 넘기는 것

 

특징

  • 애플리케이션 로딩 시점에 JPQL 문법을 체크하고 미리 파싱한다
  • 오류를 빨리 확인할 수 있다.
  • 사용하는 시점에는 파싱된 결과를 재사용하므로 성능상 이점이 있다
  • 변하지 않는 정적 SQL이 생성되므로 DB조회 성능에 도움이 된다.

@NamedQuery 어노테이션을 사용하거나 XML문서에 작성할 수 있고 우선권은 XML이 갖는다.

 

3. Criteria

3.1 Criteria 기초

  • JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API
  • 코드로 JPQL을 작성한다
  • 장점
    • 문법 오류를 컴파일 단계에서 잡을 수 있다
    • 문자 기반의 JPQL보다 동적 쿼리를 안전하게 생성할 수 있다
  • 단점
    • 코드가 복잡하고 장황해서 직관적으로 이해가 안된다.
CriteriaBuilder cb = em.CriteriaBuilder(); // 빌더 생성

CriteriaQuery<Member> cg = cb.createQuery(Member.class); // 생성, 반환 타입 지정

Root<Member> m = cq.from(Member.class); // from절 생성, 조회의 시작으로 Root
cq.select(m); // select절 생성

TypedQuery<Member> query = em.createQuery(cq);
List<Member> members = query.getResultList();

 

3.2 Criteria 쿼리 생성

CriteriaBuilder.createQuery() 메서드를 통해 쿼리 생성 가능

 

3.3 조회

// 한 건 조회
cq.select(m);
// 여러 건 조회
cq.multiselect(m.get("username"), m.get("age"));
cq.select( cb.array(m.get("username"), m.get("age")) );
// distinct
cq.multiselect(m.get("username"), m.get("age")).distinct(true);

 

3.4 집합

cq.multiselect(m.get("team").get("name"), maxAge, minAge)
.groupBy(m.get("team").get("name")) // groupby
.having(cb.gt(minAge, 10); // having

3.5 정렬

cq.select(m).where(ageGt).orderBy(cb.desc(m.get("age")));

3.6 조인

3.7 서브쿼리

메인 쿼리와 서브 쿼리가 관련이 있다면 메인 쿼리에서 사용한 별칭을 얻어서 쿼리해야 한다.

Root<Member> subM = subQuery.correlate(m) 이런 식으로 메인 쿼리의 별칭을 가져올 수 있다.

 

3.8 IN

in 사용 가능

3.9 CASE

selectCase(), whne(), otherwise() 사용 가능

3.10 파라미터 정의

파라미터 정의 후 바인딩 가능

cb.parameter(타입, 파라미터 이름) -> setParameter(파라미터 이름, 파라미터)

3.11 네이티브 함수 호출

cb.function()메소드 사용

3.12 동적 쿼리

  • 다양한 검색 조건에 따라 실행 시점에 쿼리를 생성하는 것
  • 문자 기반인 JPQL보다 코드 기반인 Criteria로 작성하는 것이 더 편리(공백이나 where, and의 위치로 인해 에러발생이 없어서)

3.13 함수 정리

p.424

3.14 Criteria 메타 모델 API

m.get("age")대신 m.get(Member_.age)처럼 문자 기반에서 정적 코드 기반으로 변경할 수 있다.

하이버네이트의 코드 생성기를 통해 메타 모델 클래스를 자동으로 만들 수 있다.

 

4. QueryDSL

  • 쿼리를 문자가 아닌 코드로 작성해도 쉽고 간결하며 그 모양도 쿼리와 비슷하게 개발 할 수 있도록
  • JPA, JDO, JDBC, Lucene, Hibernate Search, 몽고DB, 자바 컬렉션 등 다양하게 지원
JPAQuery query = new JPAQuery(em);
QItem item = QItem.item;
List<item> list = query.from(item).where(item.name.eq("좋은상품").and ..)
    .list(item); // 조회 할 프로젝션 지정

// 결과 호출 시
uniqueResult()
singleResult()
list()

 페이징, 정렬, 그룹, 조인, 서브쿼리 모두 사용 가능

 

QueryDSL은 코드로 쿼리를 작성할 수 있으면서 동적쿼리가 복잡하지 않기에 좋다.

 

5. 네이티브 SQL

  • JPQL을 사용할 수 없을 때 SQL을 직접 사용하도록 하는 기능
  • 엔티티를 조회할 수 있고 JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있다.
  • 네이티브 SQL로 SQL만 직접 사용할 뿐 나머지는 JPQL을 사용할 때와 같다.
String sql = "SELECT ID, AGE" + 
    "FROM MEMBER WHERE AGE > ?";

Query nativeQuery = em.createNativeQuery(sql, Member.class).setParameter(1,20);

List<Member> resultList = nativeQuery.getResultList();

// 결과 타입정의 없이 단순한 값으로 조회 시
createNativeQuery(String sqlString)
// 엔티티와 스칼라를 함께 조회하는 등, 매핑이 복잡해졌을 때
createNativeQuery(String sqlString, String resultSetMapping)
  • 결과 매핑 애노테이션 정리 - p.449
  • JPQL처럼 Named 네이티브 SQL을 사용해서 정적 SQL을 작성할 수 있다.
  • Query, TypeQuery를 반환한다.
  • 관리하기가 쉽지 않고, 특정 데이터베이스에 종속적인 쿼리가 증가해 이식성이 떨어지는 단점이 있지만 사용을 안할 수 없어, JPQL -> JPA 구현체 기능 -> 네이티브 SQL -> MyBatis, JdbcTemplate등을 사용하자

6. 객체지향 쿼리 심화

  • 벌크연산
    • 여러 건을 한 번에 수정하거나 삭제할 수 있다
    • executeUpdate()메소드를 사용할 수 있다.
    • 영속성 컨텍스트를 무시하고 DB에 직접 쿼리하므로 영속성 컨텍스트와 DB간 데이터 차이에 주의해야 한다.
    • 가능하면 벌크 연산을 가장 먼저 수행하고 영속성 컨텍스트를 초기화 하는 것도 필요하다
  • 영속성 컨텍스트와 JPQL
    • 영속성 컨텍스트에 엔티티가 있으면 DB에서 조회한 결과를 버리고 영속성 컨텍스트에 있던 엔티티를 반환한다.
    • 영속성 컨텍스트는 영속 상태인 엔티티의 동일성을 보장하기에 기존 엔티티를 그대로 두고, 새로 검색한 엔티티를 버린다.
  • JPQL과 플러시 모드
    • JPQL은 영속성 컨텍스트에 있는 데이터를 고려하지 않고 DB에서 데이터를 조회하므로 JPQL실행 전에 영속성 컨텍스트의 내용을 데이터베이스에 반영해야 한다.
    • 일반적으로 플러시 모드의 기본값이 AUTO라 신경 쓸 것이 없지만 COMMIT모드 사용 시에는 em.flush()로 수동으로 처리하거나 setFlushMode()로 특정 쿼리만 플러시 모드를 AUTO로 해주면 된다.
    • COMMIT모드(트랜젝션 커밋 시에만 플러시. 쿼리 실행때는 플러시 하지 않는다) 를 사용하는 이유는 플러시 횟수를 줄여 성능을 최적화 하기 위함

 

Comments