본문 바로가기
JPA

[JPA] JPQL

by 감자b 2024. 12. 29.

JPQL

객체 지향적인 방식으로 데이터베이스에 접근할 수 있도록 도와주는 쿼리 언어로 테이블이 아니라 엔티티를 대상으로 한다.

  • JPQL은 SQL을 추상화하여 만들었기 때문에 데이터베이스 벤더에 독립적
  • SQL과 문법이 유사하다. (SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원)
  • 엔티티 객체를 중심으로 개발 가능

JPQL 문법

select m from Member as m where m.age > 18
  • 엔티티와 속성은 대소문자 구분 필요
  • JPQL 키워드는 대소문자 구분 X
  • 별칭이 필수 (as 생략 가능)
  • 테이블 이름이 아닌 엔티티 이름을 사용
select	
	COUNT(m), // 회원수	
	SUM(m.age), // 나이 합
	AVG(m.age), // 평균 나이	
	MAX(m.age), // 최대 나이	
	MIN(m.age) // 최소 나이
from Member m
where ...
group by ...
having ...
order by ... // 등 모두 사용 가능

 

  • 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");

Member member = query.getSingleResult(); // 결과 하나를 반환 -> 무조건 하나만 있어야 함. 그렇지 않으면 예외 발생
List<Member> members = query.getResultList(); // 결과가 하나 이상일 때 리스트 반환

 

  • 프로젝션 - SELECT 절에 조회할 대상 지정
    • → 조회 대상 : 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입)
  • 페이징
    • setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
    • setMaxResults(int maxResult) : 조회 할 데이터 수
  • 파라미터 바인딩
SELECT m FROM Member m where m.username=:username 
query.setParameter("username", usernameParam); // 위의 username에 usernameParam을 바인딩

 

  • 조인
// 내부 조인
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
// 조인 - on 절 (조인 대상을 필터링 하는 경우, 연관관계 없는 엔티티를 외부 조인 하는 경우)
SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name

 

  • 서브 쿼리 사용 가능
    • [NOT] EXISTS (서브쿼리) : 서브쿼리에 결과가 존재하면 참
      • ALL : 모두 만족하면 참
      • ANY, SOME : 조건을 하나라도 만족하면 참
    • [NOT] IN (서브쿼리) : 서브쿼리의 결과 중 하나라도 같은 것이 있다면 참
  • 서브 쿼리에서 지원하는 함수
// 팀A에 소속인 회원
select m from Member m where exists (select t from m.team t where t.name = '팀A')

//어떤 팀이든 팀에 소속된 회원
select m from Member m where m.team = ANY (select t from Team t)

SELECT, WHERE HAVING 절에서 서브쿼리 가능

+ 과거 버전은 FROM 절에서 서브쿼리 사용을 못하였지만 최신 버전은 가능하다고 한다.

 

  • JPQL 기타 타입 표현
    • ENUM - 패키지명 포함
    • 엔티티 타입 - type(m) = member (상속 관계에서 사용)
em.createQuery("select i from Item i where type(i) = Book ", Item.class);
  • 조건식
  • 기본 case 식은 SQL과 유사
  • COALESCE : 하나씩 조회해서 null이 아니면 반환
// 사용자 이름이 없으면 '회원이름없음'을 반환
select coalesce(m.username, '회원이름없음') from Member m
  • NULLIF : 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
// 사용자 이름이 '관리자'면 null 반환, 나머지는 본인 이름 반환
select NULLIF(m.username, '관리자') from Member m

 

  • 경로 표현식
    • 상태 필드 : 단순히 값을 저장하기 위한 필드
      • 경로 탐색의 끝, 더 이상 탐색 불가
    • 연관 필드 : 연관관계를 위한 필드
      • 단일 값 연관 필드 : @ManyToOne, @OneToOne 대상이 엔티티인 경우
        • → 묵시적 내부 조인 발생, 이어서 탐색 가능 (m.team.name)
      • 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany 대상이 컬렉션인 경우
        • → 묵시적 내부 조인 발생, 더 이상 탐색 불가
        • FROM 절에서 명시적 조인을 통해 별칭을 얻는다면 해당 별칭을 통해 탐색이 가능
        • select m.username from Team t join t.members m;
    경로 탐색은 주로 select, where 절에서 사용하지만 연관관계 필드를 쿼리에 사용하면 묵시적 조인이 발생하여 from절에 영향을 미침 → 튜닝에 어려움이 있으므로 명시적으로 조인할 것
select m from Member m join m.team t // 명시적 조인의 예

 

  • fetch join
    • 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능으로 성능 최적화를 위해 사용
    • join fetch 명령어를 사용
select m from Member m join fetch m.team

-> SELECT M.*, T.* FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID = T.ID

주의 사항!

  • 페치 조인 대상에는 별칭을 줄 수 X
  • 둘 이상의 컬렉션은 페치 조인 할 수 없으며, 컬렉션을 페치 조인하면 페이징 사용 X
    • → 일대다의 경우 join을 하게 되면 row수가 늘어나기 때문
    • 중복 제거하려면 distinct 추가 → JPQL에서 distinct는 sql distinct + 애플리케이션에서 엔티티 중복 제거(같은 식별자를 가진 엔티티를 제거)
    • + 하이버네이트6부터 DISTINCT 명령어를 사용하지 않아도 애플리케이션에서 중복 제거가 자동으로 적용

일반 조인 vs 페치 조인

일반 조인 : 연관된 엔티티를 함께 조회하지 않음

→ select 절에 지정한 엔티티만 조회하고 연관관계로 맺어진 엔티티는 고려하지 않음 (지연 로딩 전략)

페치 조인 : 연관된 엔티티를 함께 조회 (즉시 로딩 전략). 객체 그래프를 SQL 한 번에 조회하는 개념

 

페치 조인의 한계

  • 페치 조인 대상에는 별칭을 줄 수 없음.
    • 하이버네이트는 가능하지만 가급적 사용 X
  • 둘 이상의 컬렉션은 페치 조인 할 수 없음.
  • 컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없음.
    • 일대일, 다대일의 경우는 페이징 가능 그렇지 않으면 메모리에서 페이징 처리를 하는데 이는 매우 위험
    해결 방법
    • XxxToOne 관계만 페치 조인 후 방법 1, 2 (컬렉션, 프록시 객체를 설정한 size 만큼 IN 쿼리를 통해 조회) 
  •  1. @BatchSize(size = 100~1000)
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
  • 2. hibernate.default_batch_fetch_size: 100
spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100
  • 3. 1,2로 안되면 DTO 조회 방법
  • 4. DTO 조회 방식이 안되면 NativeSQL, 스프링 JdbcTemplate 사용

 

여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야한다면 페치 조인이 아닌 일반 조인을 사용하고 필요한 데이터만 조회해서 DTO로 반환.

페치 조인은 객체 그래프를 유지할 때 사용하면 효과적임.

 

  • 엔티티 직접 사용
    • JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용한다.
select count(m.id) from Member m; // 엔티티의 아이디 사용
select count(m) from Member m; // 엔티티 직접 사용
// 두 쿼리는 같음
// 엔티티를 파라미터로 전달하거나, 연관관계인 엔티티를 직접 사용해도 ID로 변환되어서 쿼리가 나감.

 

 

  • Named Query
    • 미리 정의해서 이름을 부여해두고 사용하는 JPQL로 정적 쿼리만 가능
    • 애플리케이션 로딩 시점에 초기화 후 재사용
    • → 보통 JPQL은 SQL로 파싱된 후 실행되어야 함. 하지만 Named Query의 경우 로딩 시점에 딱 한 번 초기화하고 캐시해서 재사용
    • 애플리케이션 로딩 시점에 쿼리 검증 가능 (로딩 시점에 파싱이 실패한다면 오류를 발생)
    • 스프링 데이터 JPA에서 사용하는 @Query 애노테이션이 대표적인 Named Query
@Entity
@NamedQuery(
		name = "Member.findByUsername",
		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();

 

  • 벌크 연산 
    • 한 번에 많은 수의 데이터를 변경해야 한다면? → Dirty checking 방법으로는 너무 많은 쿼리가 발생
      • 쿼리 한 번으로 여러 테이블(엔티티) 로우 변경 가능
      • executeUpdate()의 결과는 영향받은 엔티티 수 반환
      • UPDATE, DELETE 지원
      • INSERT(insert into .. select, 하이버네이트 지원)
String query = "update Product p " + "set p.price = p.price * 1.1 " + "where p.stockAmount < :stockAmount";
int resultCount = em.createQuery(query)
			.setParameter("stockAmount", 10)
			.executeUpdate();

벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 날림.

따라서 벌크 연산을 먼저 실행하고 영속성 컨텍스트를 초기화할 것

→ 벌크 연산은 JPQL이므로 실행 시 flush가 발생함.

하지만 내가 만약 DB에서 데이터를 조회 후 벌크 연산을 실행한다면?

DB에는 결과가 반영되었지만 영속성 컨텍스트에는 변경 전 결과가 남아있음. 따라서 컨텍스트를 초기화하고 사용해야 안전


참고

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런

김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도

www.inflearn.com

 

'JPA' 카테고리의 다른 글

[Spring Data JPA] Page, Slice  (1) 2024.12.29
[JPA] OSIV(Open Session In View)  (1) 2024.12.29
[JPA] 데이터 타입  (0) 2024.12.29
[JPA] 영속성 전이, 고아 객체  (0) 2024.12.29
[JPA] 프록시, 로딩 전략  (0) 2024.12.29