10. 객체 지향 쿼리 언어1 - 기본

소개#

JPA는 다양한 쿼리 방법을 지원#

  • JPQL
  • JPA Criteria
  • QueryDSL
  • Native SQL
  • JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용

JPQL 소개#

  • 가장 단순한 조회 방법
    • EntityManager.find()
  • 18상 이상인 사람 모두 불러오려면 어떻게?

JPQL#

  • JPA를 사용하면 엔티티 객체 중심으로 개발 해야함
  • 문제는 검색 쿼리
  • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색
  • 앱이 필요한 데이터만 DB에서 불러오려면 결구 검색 조건이 포함된 SQL이 필요함!
  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
  • JPQL은 엔티티 객체를 대상으로 쿼리
  • SQL은 테이블을 대상으로 쿼리
  • JPQL 을 한마디로 객체 지향 쿼리

Criteria 소개#

Criteria 대신 QueryDSL 사용 권장

  • JPQL을 문자열 더하는 문제가 옴
    • 동적 쿼리로 전환하면 좋음
  • 그 대안으로 Criteria 나옴
  • 음 그런데 오히려 가독성이 떨어지는 것 같음

장점#

  • 동적쿼리 생성 시 컴파일 시점에 잡아줄 수 있음
  • 동적쿼리를 짜기 편함

단점#

  • SQL 스럽지 않음...
  • 실무에서 거의 안쓰신다고 함
  • 한두번 써봤는데 유지보수가 안됨...

QueryDSL 소개#

JPQL이 근본, 기승전 JPQL

Member m = ...;
queryFactory
.select(m)
.from(m)
.where(m.name.like("kim"))
.orderBy(m.name.desc())
.fetch()
  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있음
  • JPQL 빌더 역할
  • 컴파일시 오류검출 가능
  • 동적쿼리 작성이 편함
  • 단순하고 쉬움
  • 실무 사용 권장

Native SQL 소개#

  • JPA가 제공하는 SQL을 직접 사용하는 기능
  • JPQL로 해결할 수 없는 특정 DB에 의존적인 기능

JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용#

  • 직접 커넥션 얻어서 사용
  • JPA 쿼리들은 commit, query 시 flush 됨
    • 영속성 컨텍스트를 플러시해야 후에 JdbcTemplate 을 문제없이 사용 가능

기본 문법과 쿼리 API#

JPQL (Java Persistence Query Language)

JPQL 문법#

select_문 :: =
select_절
from_절
[where_절]
[groupby_절]
[having_절]
[orderby_절]
update_문 :: = update_절 [where_절]
delete_문 :: = delete_절 [where_절]
  • select m from Member as m where m.age > 18
  • Entity와 속성은 대소문자 구분 O (Member, m.age)
  • JPQL 키워드는 대소문자 구분 X (SELECT, From, where)
  • 엔티티 이름(@Entity(name = "")) 사용, 테이블 이름이 아님
  • 별칭은 필수(m) (as 는 생략 가능)

집합과 정렬#

select
COUNT(m),
SUM(m.age),
AVG(m.age),
MAX(m.age),
MIN(m.age)
from Member m
  • group by, order
  • having

TypeQuery, Query#

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

결과조회#

  • query.getResultList(): 결과가 하나 이상일 때
    • 결과가 없으면 빈 리스트 반환
  • query.getSingleResult(): 결과가 정확히 하나
    • 결과가 없으면: javax.persistence.NoResultException
    • 결과가 두개 이상이면: javax.persistence.NonUniqueResultException
  • Spring Data JPA 에서는
    • Exception 보단...
    • null이나 Optional 반환

파라미터 바인딩#

이름 기준#

Member singleResult2 =
em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();

위치 기준#

  • ?1 이런 식으로 가능하나 권장하지 않음

프로젝션 (SELECT)#

  • SELECT 절에 조회할 대상을 지정하는 것
  • 대상: 엔티티, 임베디드 타입, 스칼라 타입 (primitive type)
  • Entity
    • SELECT m FROM Member m
    • SELECT m.team FROM Member m
  • Embedded
    • SELECT o.adress from Order o
  • Scalar
    • SELECT m.age FROM Member m
  • DISTINCT 로 중복 제거

여러 값 조회#

1. Query 타입 조회
List resultList = em.createQuery("select m.age, m.username from Member m").getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("age = " + result[0]);
System.out.println("username = " + result[1]);
2. Object[] 타입 조회
List<Object[]> resultList = em.createQuery("select m.age, m.username from Member m").getResultList();
for (Object[] result : resultList) {
System.out.println("age = " + result[0]);
System.out.println("username = " + result[1]);
}
3. new 명령어 조회
List<MemberDto> resultList = em.createQuery(
"select new hellojpa.jpql.domain.MemberDto(m.username, m.age) from Member m",
MemberDto.class).getResultList();
for (MemberDto member : resultList) {
System.out.println("username = " + member.getUsername());
System.out.println("age = " + member.getAge());
}
  • 패키지명을 다 적어야 한다는 단점이 있음
  • 순서와 타입이 일치하는 생성자 필요

페이징#

  • setFirstResult(int startPosition): 조회 시작 위치
  • setMaxResults(int maxResult): 조회할 데이터 수
List<Member> resultList = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(10)
.setMaxResults(10)
.getResultList();

조인#

  • 내부 조인
    • SELECT m FROM Member m [INNER] JOIN m.team t
  • 외부 조인
    • SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
  • 세타 조인
    • select count(m) from Member m, Team t where m.username = t.name
    • Member row * Team row만큼 Row가 나옴...

ON 절#

1. 조인 대상 필터링#

회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인

  • JPQL
    • SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
  • SQL
    • SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID=t.id and t.name='A'

2. 연관관계 없는 엔티티 외부 조인#

회원 이름과 팀의 이름이 같은 대상 외부 조인

  • JPQL
    • SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
  • SQL
    • 비슷함

서브 쿼리#

  • 나이가 평균보다 많은 회원

select mfrom Member m where m.age > (select avg(m2.age) from Member m2)

  • 한건이라도 주문한 고객
    • 아래와 같이 하면 서브쿼리와 본 쿼리의 Member m을 공유해서 성능이 느려짐 select m from Member m where (select count(o) from Order where m = o.member) > 0

서브 쿼리 예제#

팀 A 소속인 회원

select m from Member m
where exists (select t from m.team t where t.name ='팀A')

전체 상품 각각의 재고보다 주문량이 많은 주문들

select o from Order o
where o.orderAmout > ALL(select p.stockAmout from Product p)

어떤 팀이든 팀에 소속된 회원

select m from Member m
where m.team = ANY(select t from Team t)

JPQL 서브 쿼리의 한계 ⭐️#

  • JPQL 는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
  • SELECT 절도 가능 (하이버네이트 지원)
  • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
    • 조인으로 풀 수 있으면 풀어서 해결
    • 앱에서 가져온걸 조립하거나, 쿼리를 여러개로 풀기도 함
    • 정안되면 native query

JPQL 타입 표현과 기타식#

  • 문자: 'Hello' 'What!'
  • 숫자: long 20L, double 20D, float 20F
  • Boolean: TRUE, FALSE
  • ENUM: jpabook.MemberType.Admin (패키지명 포함)
    • setParameter로 대체할 수 있음
  • 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)

그리고 표준 SQL 은 다 지원한다고 보면 됨

조건식 (CASE 등등)#

  • COALESCE: null 이면 지정된 값 반환
  • NULLIF

JPQL 함수#

  • CONCAT
  • SUBSTRING
  • TRIM
  • ...
  • SIZE, INDEX (JPA 용도)
    • 컬렉션 크기 select size(t.members) from Team t
    • INDEX 는 안쓰는게...

사용자 정의 함수 호출#

  • 사용자 정의함수를 직접 등록해서 쓸 수 있음
  • 보통 DB마다 기본적으로 제공하는 함수들은, Hibernate Dialect Function으로 등록되어 있었음
  • group_concat function이 DB에 등록되어 있어야함

추가적으로#

  • lombok 에 equals HashCode
  • JPQL, queryDSL
  • 의문사항 ElementCollection
Last updated on