JPQL 개요
JPA가 지원하는 쿼리 방법은?
1)JPQL:표준 문법으로 거의 대부분 해결
2)JPA Criteria,QueryDSL: 자바 코드로 짜서 JPQL을 빌드 해주는 제너레이터 클래스의 모음
3)Native SQL:표준 SQL 벗어날떄 Native 쿼리 직접 날린다(Oracle의 connectBY같이)
4)JDBC API 직접 사용
JPQL
필요성
테이블이 아닌 엔티티 객체를 대상으로 검색할때 유용하다
모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능하고
필요한 데이터만 DB에서 가져오려면 검색 조건이 포함된 SQL 필요
JPQL 사용예
List<Member> result=em.createQuery("select m from Member m where m.name like '%kim%'".getResultList();
Criteria
동적쿼리가 어려워서 자바 표준이 지원하는 문법이지만 복잡하고 실용성이 없어서 QueryDSL 사용 권장
NativeSQL
사용예
List<Member> resultList=em.createNativeQuery("select ID,AGE FROM MEMBER WHERE NAME='kim'",Member.class).getResultList());
참고
JPQL 날릴 떄 em.persist()한 것을 미리 저장을 해놓는다. JPA와 관련된 것은 가능이다
그러나 DB 커넥션을 획득하고 그것으로 쿼리를 날리면(JPA랑 관련 X) 미리 저장을 하지 않음
JPQL-기본
update문?
JPA는 트랜잭션 내에서 값을 바꾸면 자동으로 update 쳐주지만, 한건 한건만 바꿀 수 있다.
이 경우 한방에 여러개를 update 치기 위해서(예를 들어 전사원 연봉 10프로 증가) update 문을 가진 JPQL을 사용할 수 있다.
TypedQuery VS Query
TypeQuery는 반환타입이 명확할때 사용, Query는 반환 타입이 명확하지 않을 때 사용
TypedQuery는 select m From member m 같은 경우
Query는 select m.username, m.age 같은 경우
파라미터바인딩의 예
List<Member> resultList=em.createQuery("select m from Member m
where m.username=:username",Member.class).
setParameter("username","leee").getResultList();
결과조회 방법
getResultList
결과가 하나 이상일 때, NullpointerException 걱정 안해도 됨
getSingleResult
결과가 정확히 하나일때만 사용,나머지 경우에 예외 터진다.
프로젝션
프로젝션이란 select 절에 조회할 대상을 지정하는 것을 뜻한다
연관된 엔티티를 프로젝션할때
select m.team from Member m 보다는
select t from Member m join m.team t가 join이 잘 보이기에 좋다
distinct 중복 제거 예제
select distinct m.username,m.age from Member m
여러값을 조회할때
QueryType,Object[]타입으로 조회가능하지만
DTO로 조회하는 것이 훨씬 좋다.
사용예
List<MemberDTO> result=em.createQuery("select
new JPQL.MemberDTO(m.USERNAME,m.age) from Member m,MemberDTO.class)
.getResultList();
주의점
꼭 Dto 클래스에 생성자 쓰기!
페이징 사용법
사용예
List<Member> result=em.createQuery("select m from Member m
order by m.age desc",Member.class)
.setParameter(1)//0부터 시작하는 startPosition
.setMaxResults(20)//조회할 데이터 수
.getresultList();
조인
종류
내부조인: 조인조건을 만족하는 두 테이블의 공통된 값을 기반으로 join을 수행하는 것을
내부 join이라고 하며 멤버는 있고 팀이 없다면 출력하지 않는다
외부 조인: 조인 조건을 만족하지 않는 레코드도 결과에 포함한다. 외부 join은 팀이 없어도 멤버는 조인이 가능하다
세타조인: 연관관계 전혀 없는지 비교할때 사용한다
ON절 활용하여
1)조인대상 필터링
2)연관관계 없는 엔티티를 외부조인이 가능하다
서브쿼리
예
select m from Member m where m.age>(select avg(m2.age) from Member m2)
이 경우 m2 를 새로 정의 해서 성능이 잘 나온다. 하지만
select m from Member m where (
select count(o) from Order o where m=o.member)>0
이 경우 m을 긁어오기에 성능 안나올수 있다.
서브쿼리 지원함수
EXISTS
서브쿼리에 결과가 존재하면 참.
All: 모두 만족시 참
ANY,SOME:같은 의미
IN
서브 쿼리의 결과 중 하나라도 같은 것이 있으면 참
사용예
select m from Member m where
exists (select t from m.team t where t.name='팀A')
select o from Order o
where o.orderAmount>ALL(select p.stockAmount from Product p)
select m from Member m
where m.team=ANY(select t from Team t)
JPQL타입을 표현할때는
문자는 ''사이에
숫자는 10L,10D,10F
Boolean:TRUE,FALSE로
사용예
String query="select m.username,'hello', true FROM Member m";
List<Object[]> result=...
Object[0]:유저이름,Object[1]:Hello,Object[2]:true 들어간다
ENUMTYPE사용예
where m.type=jpql(클래스).MemberType(클래스명).Admin;
상속관계에서 엔티티 타입 구분 시 예제
"select i from Item i where type(i)=Book",Item.class)
type이 book인 것만 추출한다
조건식-CASE식
예제
select
case when m.age <= 10 them '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
COALESE:하나씩 조회해서 NULL이 아니면 반환
예제
select coalesce(m.username,'이름 없는 회원') from Member m
username 없으면 이름 없는 회원으로 나온다.
NULLIF:두 값이 같으면 NULL반환, 다르면 첫번째 값
예제
select NULLIF(m.username,'관리자') from Member m
JPQL함수
기본함수와 사용자정의함수로 나누어져 있으며
기본함수에는
문자 두개 더하는 concat
문자 자르는 SUBSTRING
공백 제거하는 TRIM
대소문자로 전환하는 LOWER,UPPER
문자길이 찾는 LENGTH
문자열에서 문자열 위치 찾는 LOCATE
ABS,SQRT,MOD 같은 수학함수
콜렉션에서 사이즈 계산할 수 있는 size함수
등이 있다.
사용자 정의함수는 교재 참고
JPQL-경로표현식
상태필드: 단순히 값 저장 위한 필드(ex.m.username)
연관필드:1)단일값 연관 필드 2)컬렉션 값 연관 필드
연관필드들은 묵시적 내부 조인이 발생할 수 있다.
단일값 연관 경로는 select m.teamname 처럼 탐색이 가능하지만
컬렉션 값 연관 경로는 묵시적 join으로는 탐색이 불가능 하니(t.members.size가 최선) 명시적 join 으로 해결가능
참고
컬렉션 값 연관 경로 가져올떄 List result로 받는다
결론
명시적 join쓰자
페치조인
페치조인의 필요성
일단 기본적으로 type을 LAZY 로 하는 이유는 조회할때 한 방에 다른 엔티티까지 쿼리가 나가고 그거에 연관된 엔티티까지 쿼리가 나가면 비효율 적이다.
그래서 LAZY 를 쓰는데 조회할때 다른 Entity 의 값도 가져오려면 LAZY 타입이니 쿼리가 많이 나가서 fetch JOIN 으로 한 쿼리로 연결 시킨다.
그러면 EAGER 바꾸면 될 것같지만, EAGER 는 예측할 수 없는 문제도 있고, 다른곳에서 조회할때는 또 다른 엔티티의 값이 필요가 없을 수도 있기 때문에 기본은 LAZY로 해두고 다른 엔티티의 값이 필요한 특수한 경우에만 fetch JOIN 을 한다.
즉 객체 그래프를 동적으로 명시적으로 가지고 오고 싶을 때 fetch join 을 쓴다
N+1문제를 fetch join으로 해결가능하다
1대 다 관계에서 fetch join
데이터가 뻥튀기 될 수 있다.
select t from Team t join fetch t.members
이러면 데이터베이스 테이블을 따라서 memberID랑 Name만 다른 것을 다른 컬럼으로 본다
같은 식별자를 가진 Team 엔티티를 제거하려면 distinct를 써주면 된다
페치조인VS일반조인
일반 조인은 실행 시에 연관된 엔티티를 함께 퍼올리지 않는다
페치조인 한계
1)페치조인 대상 별칭 불가->fetch join 을 여러개 쓸때만 가능
2)둘이상의 컬렉션 페치 조인 불가->다대다 데이터 뻥튀기
3)컬렉션을 페치 조인시 페이징 API 사용 불가:DB에서 데이터를 다 끌고 와서 메모리에서 페이징 하기 때문에 장애나기 매우 좋다
사용을 원한다면?
@OneToMany위에 @Batchsize(size=100) 혹은 global setting 으로 배치 size를 이용해서 해결
페치조인보다는 일반조인
여러 테이블 조인해서 엔티티가 가진 모양이 아닌 다른 결과를 할때 일반 조인을 사용해서 필요한 데이터만 DTO로 반환하는 것이 효과적
다형성 쿼리
조회 대상을 특정 자식으로 한정할떄
select i from Item i where type(i) IN (BOOk,Movie)//Item 중 Book,Movie를 조회해라
상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 떄
select i from Item i where treat(i as Book).author='kim'
엔티티를 직접 사용
기본키값 외래 키값 모두 JPQL에서 엔티티를 직접 사용하면 SQL에서는 해당 엔티티의 기본
키값을 사용
Named 쿼리
미리 정의해서 이름을 부여해두고 사용하는 JPQL인데
정적 쿼리만 가능하기 떄문에
장점 1) 문법 오류를 잡아줌
장점 2) SQL로 파싱 후 캐시 하기 때문에 파싱 비용이 별로 없다
어떻게 사용
1)어노테이션
2)XML에 정의
3)Spring data JPA 활용
벌크연산
PK로 찍어서 update,delete 제외한 나머지 모든 SQL 의 UPDate 문이나 delete 문
JPA의 한건한건의 변경감지 대신 쿼리 한번으로 update 치기 가능
예제
int resultCount=em.createQuery("update Member m st m.age=20").executeUpdate();
주의점
영속성 컨텍스트를 무시하고 db에 직접 쿼리 날리므로
DB에는 update 되어 6000인데 영속성 컨텍스트에는 원래 값이 들어가 있는 경우가 있을 수 있다
1)벌크 연산을 먼저 실행하거나
2)벌크 연산 수행 후 영속성 컨텍스트를 초기화한다