일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- exclusive lock
- 스프링 폼
- 즉시로딩
- 지연로딩
- 데코레이터
- PS
- 다대다
- SQL프로그래밍
- querydsl
- 동적sql
- execute
- 낙관적락
- JPQL
- CHECK OPTION
- 일대다
- 연관관계
- FetchType
- 유니크제약조건
- 비관적락
- dfs
- shared lock
- 백트래킹
- 스토어드 프로시저
- 연결리스트
- BOJ
- fetch
- eager
- 힙
- 다대일
- 이진탐색
- Today
- Total
흰 스타렉스에서 내가 내리지
[JPA] JPQL 본문
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
for(Member member : resultList){
System.out.println("member = " + member);
}
em.createQuery()의 두 번째 파라미터에 반환할 타입을 지정하면 TypeQuery를 반환하고,
지정하지 않으면 Query를 반환한다.
Query query = em.createQuery("SELECT m.name, m.address from Member m");
List resultList = query.getResultList();
for(Object o : resultList){
Object[] result = (Object[]) o; // 결과가 둘 이상이면 Object[] 반환
System.out.println("name = " + result[0]);
System.out.println("address = " + result[1]);
}
SELECT 절에서 여러 엔티티나 컬럼을 선택할 때는 반환할 타입이 명확하지 않으므로 Query 객체를 사용한다.
결과조회
- query.getResultList() :
- 결과를 리스트로 반환한다. 만약 결과가 없으면 빈 컬렉션을 반환한다.
- query.getSingleResult() :
- 결과가 없으면 NoResultException 예외 발생
- 결과가 1개보다 많으면 NonUniqueResultException 예외 발생
파라미터 바인딩
String usernameParam = "User1";
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.name = :name", Member.class);
query.setParameter("name", usernameParam);
List<Member> resultList = query.getResultList();
String usernameParam = "User1";
List<Member> members =
em.createQuery("SELECT m FROM Member m where m.name = :name", Member.class)
.setParameter("name", usernameParam)
.getResultList();
JPQL API는 대부분 메소드 체인 방식으로 설계되어 있어서 다음과 같이 연속해서 작성할 수 있다.
NEW 명령어
TypedQuery<MemberDTO> query =
em.createQuery("SELECT new com.joos.jpastudy.dto.MemberDTO(m.name, m.age) FROM Member m", MemberDTO.class);
List<MemberDTO> resultList = query.getResultList();
SELECT 다음에 NEW 명령어를 사용하면 반환받을 클래스를 지정할 수 있는데 이 클래스의 생성자에 JPQL 조회 결과를 넘겨줄 수 있다.
그리고 NEW 명령어를 사용한 클래스로 TypeQuery 를 사용할 수 있어서 지루한 객체 변환 작업을 줄일 수 있다.
페이징 API
TypedQuery<Member> query =
em.createQuery("SELECT m FROM Member m ORDER BY m.name DESC", Member.class);
query.setFirstResult(10);
query.setMaxResults(20);
query.getResultList();
- setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작한다)
- setMaxResults(int maxResult) : 조회할 데이터 수
위 코드를 보면, FirstResult의 시작은 10이므로, 11번째부터 시작해서 총 20건의 데이터를 조회한다.
따라서 11번 ~ 30번 데이터를 조회한다.
통계쿼리
Double orderAmountAvg =
em.createQuery("SELECT AVG(o.orderAmount) FROM Order o", Double.class)
.getSingleResult();
집합 함수 종류
- COUNT
- MAX, MIN
- AVG
- SUM
SELECT t.name, COUNT(m.age), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
FROM Member m LEFT JOIN m.team t
GROUP BY t.name
ㄴ 팀 이름을 기준으로 그룹 별로 묶어서 통계 데이터 구하기
SELECT t.name, COUNT(m.age), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
FROM Member m LEFT JOIN m.team t
GROUP BY t.name
HAVING AVG(m.age) >= 10
ㄴ HAVING은 GROUP BY와 함께 사용하는데 GROUP BY로 그룹화한 통계 데이터를 기준으로 필터링 한다.
ㄴ 위 코드는 그룹별 통계 데이터 중에서 평균 나이가 10살 이상인 그룹을 조회한다.
이런 쿼리들을 보통 리포팅 쿼리나 통계 쿼리라 한다.
이러한 통계 쿼리를 잘 활용하면 애플리케이션으로 수십 라인을 작성할 코드도 단 몇 줄이면 처리할 수 있다.
하지만 통계 쿼리는 보통 전체 데이터를 기준으로 처리하므로 실시간으로 사용하기엔 부담이 많다.
결과가 아주 많다면 통계 결과만 저장하는 테이블을 별도로 만들어 두고 사용자가 적은 새벽에 통계 쿼리를 실행해서 그 결과를 보관하는 것이 좋다.
정렬 (ORDER BY)
SELECT m from Member m order by m.age DESC, m.username ASC
select t.name, COUNT(m.age) as cnt
from Member m LEFT JOIN m.team t
GROUP BY t.name
ORDER BY cnt
JPQL 조인
JPQL은 JOIN 명령어 다음에 조인할 객체의 연관 필드를 사용한다.
SELECT m.username, t.name
FROM Member m JOIN m.team t
WHERE t.name = '팀A'
ORDER BY m.age DESC
ㄴ 위 쿼리는 '팀A' 소속인 회원을 나이 내림차순으로 정렬하고 회원명과 팀명을 조회한다.
만약 조인한 두 개의 엔티티를 조회하려면 다음과 같이.
SELECT m, t
FROM Member m JOIN m.team t
//
List<Object[]> result = em.createQuery(query).getResultList();
for(Object[] row : result){
Member member = (Member) row[0];
Team team = (Team) row[1];
}
외부조인은 다음과 같이.
SELECT m
FROM Member m LEFT [OUTER] JOIN m.team t
OUTER는 생략 가능해서 보통 LEFT JOIN으로 사용한다.
페치 조인
연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능이다.
select m
from Member m join fetch m.team
ㄴ 이렇게 하면 연관된 엔티티나 컬렉션을 함께 조회하는데 여기서는 회원(m)과 팀(m.team)을 함께 조회한다.
* 페치 조인을 사용하면 SQL 한 번으로 연관된 엔티티들을 함께 조회할 수 잇어서 SQL 호출 횟수를 줄여 성능을 최적화할 수 있다.
페치 조인은 글로벌 로딩 전략보다 우선한다.
예를 들어 글로벌 로딩 전략을 지연 로딩으로 설정해도 JPQL에서 페치 조인을 사용하면 페치 조인을 적용해서 함께 조회한다.
글로벌 로딩 전략은 될 수 있으면 지연 로딩을 사용하고 최적화가 필요하면 페치 조인을 적용하는 것이 효과적이다.
컬렉션 값 연관 경로 탐색
select t.members from Team t // 성공
select t.members.username from Team t // 실패
t.members 처럼 컬렉션까지는 경로 탐색이 가능하다.
하지만 t.members.username 처럼 컬렉션에서 경로 탐색을 시작하는 것은 허락하지 않는다.
만약 컬렉션에서 경로 탐색을 하고 싶으면 다음 코드 처럼 조인을 사용해서 새로운 별칭을 획득해야 한다.
select m.username from Team t join t.members m
참고로 컬렉션은 컬렉션의 크기를 구할 수 있는 size 라는 특별한 기능을 사용할 수 있다.
size를 사용하면 COUNT 함수를 사용하는 SQL로 적절히 변환된다.
select t.members.size from Team t
컬렉션은 경로 탐색의 끝이다. 컬렉션에서 경로 탐색을 하려면 명시적으로 조인해서 별칭을 얻어야 한다.
서브 쿼리
나이가 평균보다 많은 회원 찾기
select m from Member m
where m.age > (select avg(m2.age) from Member m2)
한 건이라도 주문한 고객 찾기
select m from Member m
where (select count(o) from Order o where m = o.member) > 0
select m from Member m
where m.orders.size > 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.orderAmount > ALL (select p.stockAmount from Product p)
어떤 팀이든 팀에 소속된 회원
select m from Member m
where m.team = ANY (select t from Team t)
20세 이상을 보유한 팀
select t from Team t
where t IN (select t2 From Team t2 JOIN t2.members m2 where m2.age >= 20)
IN : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참이다. 참고로 IN은 서브 쿼리가 아닌 곳에서도 사용한다.
나이가 10~20인 회원을 찾아라.
select m from Member m
where m.age between 10 and 20
이름이 회원1이나 회원2 인 회원을 찾아라
select m from Member m
where m.username in ('회원1', '회원2')
Like : 문자 표현식과 패턴 값을 비교한다.
- % : 아무 값들이 입력되어도 된다. (값이 없어도 됨)
- _ : 한 글자는 아무 값이 입력되어도 되지만 값이 있어야 한다.
// 중간에 원이라는 단어가 들어간 회원 (좋은 회원, 회원, 원)
select m from Member m
where m.username like '%원%'
// 처음에 회원이라는 단어가 포함 (회원1, 회원ABC)
where m.username like '회원%'
// 마지막에 회원이라는 단어가 포함 (좋은 회원, A회원)
where m.username like '%회원'
// 회원A, 회원1
where m.username like '회원_'
// 회원3
where m.username like '__3'
// 회원%
where m.username like '회원\%' ESCAPE '\'
CASE 식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인세티브105%'
end
from Team t
// 스칼라식을 차례대로 조회해서 null이 아니면 반환한다.
// m.username이 nulldlaus '이름 없는 회원'을 반환하라
select coalesce(m.username, '이름 없는 회원') from Member m
'Spring' 카테고리의 다른 글
Swagger 에서 Pageable 인터페이스의 쿼리 파라미터를 제대로 표현하지 못하는 에러 해결 (0) | 2023.05.31 |
---|---|
스프링 데이터 JPA (0) | 2023.02.26 |
[JPA] JPQL, Criteria, QueryDSL 소개 (0) | 2023.02.25 |
[JPA] 값 타입2 - 값 타입 컬렉션 (0) | 2023.02.19 |
[JPA] 값 타입1 - 복합, 연관관계, 재정의, 복사, 비교 (0) | 2023.02.19 |