본문 바로가기
공부/Spring

[JPA] 쿼리 문법

by JERO__ 2022. 7. 18.

JPQL

JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리언어 지원

  • 테이블이 아닌 객체를 대상으로 검색
  • 객체 지향 SQL

Creteria

유지보수 어려움

대신, QueryDSL 사용

JPQL 문법

1. TypeQuery, Query

  • TypeQuery: 반환 타입이 명확할 때 사용
  • Query: 반환 타입이 명확하지 않을 때 사용
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
Query query = em.createQuery("SELECT m.username, m.age from Member m");

1-1. query.getResultList()

결과가 하나 이상일 때, 리스트 반환

1-2. query.getSingleResult()

결과가 정확히 하나, 단일 객체 반환

  • 결과가 없거나 2개 이상일 경우 에러 발생

1-3. 파라미터 바인딩

TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username=:username");
query.setParameter("username", usernameParam);

2. 프로젝션 : SELECT절 지정

SELECT m FROM Member m
SELECT distinct m.username, m.age FROM Member m
  • 단순 값을 DTO로 바로 조회
SELECT new jpabook.jpql.UserDTO(m.username, m.age) 
FROM Member m

3. 페이징 API

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

4. 조인

  • 내부 조인 : 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

Join의 On절 사용가능

SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = 'A'

5. 서브쿼리

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

서브쿼리 지원함수

  • EXISTS
  • ALL
  • ANY, SOME(같은의미)
  • [NOT] IN
select m from Member m where exists (select t from m.team t where t.name = ‘TEAM_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)

JPA 서브쿼리의 한계

FROM 절의 서브쿼리는 JPQL에서 불가능하다.

6. 조건식 - CASE식

  • 기본 CASE식
  • 단순 CASE식
select
	 case t.name 
		 when '팀A' then '인센티브110%'
		 when '팀B' then '인센티브120%'
		 else '인센티브105%'
	end
from Team t
  • COALESCE : 하나씩 조회해서 NULL이 아니면 반환
select coalesce(m.username,'이름 없는 회원') from Member m
  • NULLIF : 두 값이 같으면 NULL반환, 다르면 첫번째 값 반환

사용자 이름이 ‘관리자’면 null을 반환하고 나머지는 본인의 이름을 반환

select NULLIF(m.username, '관리자') from Member m

7. 엔티티 직접 사용

[JPQL]
select count(m.id) from Member m //엔티티의 아이디를 사용
select count(m) from Member m //엔티티를 직접 사용
  • 결국 같다.

8. Named 쿼리

  • 미리 정의해서 이름을 부여하고 사용하는 JPQL
  • 정적쿼리
  • 어노테이션, XML에 정의
  • 애플리케이션 로딩 시점에 초기화 후 재사용 → 로딩 시점에 쿼리를 검증
@Entity
@NamedQuery(
	name = "Member.findByUsername",                            // name
	query="select m from Member m where m.username = :username")  // 쿼리
public class Member {
}
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
	.setParameter("username", "회원1")
	.getResultList();

9. 벌크 연산 executeUpdate()

  • JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL 실행
    1. 재고가 10개 미만인 상품을 리스트로 조회
    2. 상품 엔티티 가격을 10% 증가
    3. 트랜잭션 커밋 시점에 변경감지가 작동
  • 변경된 데이터가 100건이면 100번의 update sql 실행
String qlString = "update Product p set p.price = p.price * 1.1 " + 
									 "where p.stockAmount < :stockAmount"; 

int resultCount = em.createQuery(qlString)  // flush 자동 호출됨
	.setParameter("stockAmount", 10) 
	.executeUpdate();

em.clear();                        // 영속성 컨텍스트 초기화해주기
  • 영속성 컨텍스트 무시 → DB에 직접 쿼리
  • 연산 수행 후 영속성 컨텍스트 초기화해주기 (안해주면 꼬일 수 있다)

댓글