JPA의 다양한 쿼리 방법
JPQL
테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
JPA Criteria
JPQL의 경우 동적 쿼리가 필요할 때 String 연산으로 처리해야함 따라서 코드로 동적으로 처리하기 편하도록 하기 위해 개발됨
criteriaQuery.select().where()... 와 같이 사용
너무 복잡하고 실용성이 없음
QueryDSL
JPQ Criteria 와 마찬가지로 문자가 아닌 자바 코드로 SQL을 작성할 수 있음
동적쿼리를 작성하기 편리하며 라이브러리자체가 단순하고 쉬움
실무에서 자주 사용
네이티브 SQL
JPA에서 제공하는 직접 SQL을 사용하는 기능
JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능을 사용할 수 있음
em.createNativeQuery(String sql)
JDBC API 직접 사용 or MyBatis, SpringJdbcTemplate 함께 사용
JPA와 관련없이 JDBC를 직접 활용하는 방법이기 때문에 영속성 컨텍스트와도 관련이 없음. 따라서, 사용 전 em.flush()를 통해 영속성 컨텍스트를 반영해야 의도한대로 동작함
JPQL 문법
엔티티와 속성은 대소문자 구분 O(Member, age)
JPQL 키워드는 대소문자 구분 X(select, WHERE)
별칭은 필수
em.createQuery는 반환 타입이 명확할 경우 TypedQuery<T>, 명확하지 않을 경우 Query 를 반환
JPQL 결과 조회 API
query.getResultList()
결과를 리스트로 반환
결과가 없을 시 빈 리스트를 반환
query.getSingleResult()
결과를 딱 한개만 반환
결과가 없으면 NoResultException
결과가 2개 이상이면 NonUniqueResultException
파라미터 바인딩
where username = :name 과 같이 ':' 을 붙여 JPQL에 사용
이후 query.setParameter("name", "깐지닉"); 과 같이 설정할 수 있음
where username = ?1 과 같이 위치를 기준으로 사용할 수도 있으나, 권장하지 않음 (디버깅이 어려움)
프로젝션
SELECT 절에 조회할 대상을 지정하는 것
엔티티 프로젝션
JPQL의 결과가 엔티티일때 일어남
엔티티를 조회할 경우 결과 엔티티들이 모두 영속성 컨텍스트에 저장됨
스칼라 프로젝션, 임베디드 타입 프로젝션 등 다양하게 존재
프로젝션 - 여러 값 조회
SELECT m.username, m.age FROM Member m
Query 타입으로 조회 - resultList 에 Object 가 들어가 있음, Object[]로 타입 캐스트하여 사용
Object[] 타입으로 조회 - resultList 에 Object[]가 들어가 있어 타입 캐스트를 할 필요 없음
new 명령어로 조회 - 반환 값을 담을 수 있는 DTO에 new 명령어를 통해 생성해서 반환해줌
SELECT new jpql.dto.MemberDTO(m.username, m.age) FROM Member m
페이징 API
query.setFirstResult(int n) n번째 데이터부터 가져오도록 설정
query.setMaxResults(int m) 최대 m개의 데이터를 가져오도록 설정
JPQL 조인
내부 조인 - 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;
JPQL ON 사용하기
조인 대상 필터링
SELECT m FROM Member m LEFT JOIN m.team t on t.name = 'A team';
연관관계 없는 엔티티 외부 조인
SELECT m FROM Member m LEFT JOIN Team t on m.username = t.name;
서브 쿼리
JPQL도 SQL과 똑같이 서브쿼리를 사용할 수 있음
[NOT] EXISTS (sub) : 서브쿼리 내 결과가 존재하면 참
[NOT] IN (sub) : 서브쿼리 내 결과 중 하나라도 같은 것이 있으면 참
{ALL | ANY | SOME} (sub)
ALL - 모두 만족하면 참
ANY, SOME - 같은 의미, 조건을 하나라도 만족하면 참
JPA 서브 쿼리 한계
JPA는 WHERE, HAVING 절에만 서브 쿼리 사용이 가능함(표준 스펙)
하이버네이트에서는 SELECT 에서도 가능함
FROM 절의 서브 쿼리는 현재 JPQL에서 불가능 (왠만하면 조인으로 풀자)
JPQL 타입 표현
문자: 'HELLO', 'She''s'
숫자: 10L, 10D, 10F
Boolean: TRUE, FALSE
ENUM: jpql.enum.MemberType.ADMIN (패키지명 포함)
엔티티 타입: TYPE(m) = Member (상속관계에서 사용)
그 외 비교 연산자, IS NULL 등 SQL 처럼 모두 사용 가능함
JPQL 조건식
기본 CASE 식, 단순 CASE 식 모두 지원
COALESCE
select coalesce(m.username, '이름 없는 회원') ... 와 같이 사용
null 이면 2번째 인자값을 반환, null이 아니면 첫번째 값 반환
NULLIF
select nullif(m.username, '관리자') ... 와 같이 사용
두 값이 같으면 null, 아니면 첫번째 값 반환 (특정 값을 제외하고 뽑고 싶을 때)
JPQL 함수
기본 함수 (CONCAT, TRIM, UPPER ...)
JPA 용 함수
SIZE: 컬렉션의 사이즈를 리턴하는 함수
INDEX: @OrderColumn과 함께 사용하여 n번째 데이터를 가져오는 함수
사용자 정의 함수 호출
select function('group_concat', i.name) from Item i
사용하기 위해서는 직접 DB 방언에 등록해야함
방언을 상속받아 추가할 함수를 등록함 (코드와 공식문서 참고)
자주 쓰는 함수들은 하이버네이트가 구현해둠