1. 개요
김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 을 들으면서 정리하는 포스팅입니다.
https://www.inflearn.com/course/ORM-JPA-Basic
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com
2. JPQL이란?
실제 애플리케이션을 개발할 때에는 필요한 데이터를 가져오기 위해서는 다양한 검색조건과 복잡한 조인쿼리를 작성해야할 때가 많다.
JPA에서는 검색조건과 조인쿼리를 해결하기 위해 SQL을 추상화 한 JPQL이라는 객체 지향 쿼리를 제공하며, JPQL은 SQL과 매우 유사한 구조를 가진다.
SQL과 JPQL의 가장 큰 차이점은 SQL은 테이블을 대상으로 쿼리를 하는 반면, JPQL은 엔티티(객체)를 대상으로 쿼리한다는 점이다.
SQL Vs. JPQL
// SQL
SELECT id, username, age FROM Member
// JPQL
SELECT m FROM Member m
JPA 환경에서 복잡한 쿼리를 작성하기 위해 사용할 수 있는 4가지 방법
- JPQL: 객체지향 쿼리를 작성할 수 있는 기본적인 방식
- Criteria: 동적쿼리 작성에 유리하지만 가독성이 떨어짐.
- QueryDSL: 동적 쿼리를 직관적으로 작성할 수 있어, 실무에서 가장 많이 사용.
- Native Query: 복잡한 SQL을 직접 작성할 수 있음.
- JDBC, Mybatis 등 : 전통적인 방식으로 SQL 처리
JPA 환경에서 복잡한 쿼리를 작성하기 위해서는 JPQL, Criteria, QueryDSL, Native 쿼리, JDBC 등을 사용할 수 있지만, JPQL과 QueryDSL만 사용하면 대부분의 문제는 해결할 수 있다. 실무에서는 대부분 QueryDSL을 사용하며, JPQL을 이해하면 QueryDSL은 금방 배워서 활용할 수 있다.
이번 포스팅에서는 JPQL 위주로 설명을 이어가겠다.
3. JPQL과 TypeQuery
A. JPQL
위에서 언급했듯이, JPQL은 엔티티를 대상으로 하는 객체지향 쿼리 언어이다.
JPA 내부적으로 JPQL을 SQL로 변환하여 실행한다.
JPQL 기본 문법
--- select 문법
select_절
from_절
[where_절]
[groupby_절]
[having_절]
[orderby_절]
--- update 문법
update_절
[where_절]
-- delete 문법
delete_절
[where_절]
B. TypeQuery
TypeQuery는 결과를 특정 타입의 객체로 반환할 수 있도록 도와준다.
이를 사용하면 쿼리 결과를 타입 안전하게 처리할 수 있다.
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> result = query.getResultList();
4. 프로젝션(SELECT)
프로젝션은 SELECT 절에서 조회할 대상을 지정하여 반환하는 작업을 뜻한다.
엔티티, 임베디드 타입, 스칼라 타입, DTO를 프로젝션으로 지정할 수 있다.
// 엔티티 프로젝션
SELECT m FROM Member m
SELECT m.team FROM Member m
// 임베디드 타입 프로젝션(Member는 Address라는 임베디드 타입을 가지는 경우)
SELECT m.address FROM Member m
// 스칼라 타입
SELECT m.username, m.age FROM Member m
// DTO 프로젝션
SELECT new com.example.dto.MemberDTO(m.username, m.age) FROM Member m
5. 페이징 API
페이징 API를 사용하면 복잡한 쿼리를 작성할 필요없이 아주 편리하게 페이징 기능을 구현할 수 있다.
List<Member> resultList1 = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(0) // 시작 위치(0번째부터)
.setMaxResults(10) // 최대 결과 수(10개)
.getResultList();
6. 조인
A. 명시적 조인
A-1. 내부 조인
- 내부 조인은 두 엔티티 간에 매칭되는 데이터만 조회할 때 사용한다.
- 즉, 조인 대상이 존재하지 않는 경우, 해당 데이터는 조회되지 않는다.
--- JPQL
SELECT m FROM Member m JOIN m.team t WHERE t.name = 'TeamA'
--- SQL
SELECT m.*
FROM Member m
INNER JOIN Team t ON m.team_id = t.id
WHERE t.name = 'TeamA'
A-2. 외부조인
- 조인 대상이 존재하지 않더라도 기준 엔티티의 데이터를 유지하며, 조인 대상이 없으면 NULL을 반환한다.
--- JPQL
SELECT m FROM Member m LEFT JOIN m.team t
--- SQL
SELECT m.*, t.*
FROM Member m
LEFT JOIN Team t ON m.team_id = t.id
A-3. 세타조인
- 엔티티를 특정 조건으로 조인하는 방식이다.
- ON 절 대신 사용하여 연관관계가 없는 엔티티를 조인할 때 사용이 가능하다.
- Cross Join + Where 조건 필터링으로 데이터를 조회한다.
--- JPQL
SELECT m, p FROM Member m, Product p WHERE m.age > p.price
--- SQL
SELECT m.*, p.*
FROM Member m, Product p
WHERE m.age > p.price
A-4 ON 절
- ON 절을 활용하면 조인 대상을 필터링하거나 연관관계가 없는 엔티티 외부 조인이 가능하다.
--- 조건 필터링
SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = 'TeamA'
--- 연관관계가 없는 엔티티 외부 조인
SELECT m, o FROM Member m LEFT JOIN Order o ON m.username = o.customerName
B. 묵시적 조인(경로 탐색)
- 객체 그래프 탐색을 통해 묵시적으로 조인을 수행할 수 있다.
- 하지만 예상치 못한 SQL 쿼리가 발생하는 것을 방지하기 이해 명시적 조인을 사용하는 것을 권장한다.
--- Member 엔티티에서 Team 엔티티의 이름을 조회하는 경우
SELECT m.team.name FROM Member m
7. 패치 조인
- SQL에서 제공하는 JOIN 종류가 아니다.
- 연관된 엔티티나 컬렉션을 한번에 조회할 때 사용한다.
- JPQL에서 성능을 최적화할 때 사용할 수 있다.
- N + 1 문제를 부분적으로 해결할 수 있다.
--- JPQL
SELECT m FROM Member m JOIN FETCH m.team
--- SQL
SELECT m.*, t.*
FROM Member m
INNER JOIN Team t ON m.team_id = t.id
N + 1 문제와 해결 방법에 대해서 궁금하다면 아래 게시글을 참고바란다.
https://soonmin.tistory.com/126
8. 서브 쿼리
A. 서브 쿼리란?
- 쿼리 안에 또 다른 쿼리가 포함된 형태로, 특정 조건을 만족하는 데이터를 조회할 때 사용한다.
- JPA에서는 WHERE, HAVING, SELECT 절에서만 서브 쿼리를 사용할 수 있다.
--- 특정 팀에서 가장 나이가 많은 회원 조회
SELECT m FROM Member m
WHERE m.age = (SELECT MAX(m2.age) FROM Member m2)
B. 서브 쿼리 지원 함수
서브쿼리 지원함수
함수 | 설명 |
EXISTS | 서브 쿼리에 결과가 존재하면 참 |
IN | 서브 쿼리의 결과 중 하나라도 같은 것이 있으면 참 |
ALL | 서브 쿼리의 모든 결과와 비교하여 모두 만족하면 참 |
ANY, SOME | 서브 쿼리의 하나라도 만족하면 참 |
서브쿼리 지원함수 사용예제
--- EXISTS 예제
--- 특정 팀에 소속된 회원이 존재하는 경우만 조회
SELECT t FROM Team t
WHERE EXISTS (SELECT m FROM Member m WHERE m.team = t)
--- IN 사용 예제
--- 30세 이상인 회원이 속한 모든 팀 조회
SELECT t FROM Team t
WHERE t IN (SELECT m.team FROM Member m WHERE m.age >= 30)
--- ALL 사용 예제
--- 모든 회원의 나이보다 나이가 많은 회원 조회
SELECT m FROM Member m
WHERE m.age > ALL (SELECT m2.age FROM Member m2)
--- ANY, SOME 사용예제
--- 같은 팀내에서 가장 어린 회원 조회
SELECT m FROM Member m
WHERE m.age <= ANY (SELECT m2.age FROM Member m2 WHERE m2.team = m.team)
9. JPQL 타입표현과 기타표현식
- JPQL에서 기본적인 타입 표현식을 지원한다.
표현식 | 설명 |
>, <, <=, >=, <>, =, !=, | 비교 연산자 |
AND, OR, NOT | 논리 연산자 |
BETWEEN | 범위 탐색 |
IN | 여러 값 검색 |
LIKE | 패턴 검색 |
EXISTS, NOT EXISTS | 존재 여부 확인 |
IS NULL, IS NOT NULL | NULL 체크 |
10. 조건식(CASE, COALESCE, NULLIF)
- JPQL에서 조건식에 따라 데이터를 조정할 수 있다.
--- CASE: 특정 조건에 따라 값을 반환
SELECT
CASE
WHEN m.age >= 18 THEN '성인'
ELSE '미성년자'
END
FROM Member m
--- COALESCE: NULL일 경우 대체값을 반환
SELECT COALESCE(m.name, '이름 없음') FROM Member m
--- NULLIF: 두 값이 같으면 NULL 반환, 다르면 첫 번째 값 반환
SELECT NULLIF(m.name, 'admin') FROM Member m
11. JPQL 함수
함수 | 설명 |
CONCAT(a, b) | 문자열 합치기 |
SUBSTRING(str, start, length) | 부분 문자열 추출 |
LOWER(str), UPPER(str) | 소문자, 대문자 변환 |
LENGTH(str) | 문자열 길이 반환 |
ABS(x) | 절댓값 |
SQRT(x) | 제곱근 |
MOD(x, y) | 나머지 연산 |
CURRENT_DATE | 현재 날짜 |
CURRENT_TIME | 현재 시간 |
CURRENT_TIMESTAMP | 현재 날짜 + 시간 |
12. Named 쿼리
- Named 쿼리를 사용하면, 정적인 쿼리를 미리 정의하여 이름을 통해 재사용할 수 있다.
- 애플리케이션 로딩 시점에 초기화 후 재사용한다.
- 애플리케이션 로딩 시점에 쿼리를 검증할 수 있다.
@NamedQuery(
name = "Member.findByUsername",
query = "SELECT m FROM Member m WHERE m.username = :username"
)
public class Member { ... }
List<Member> members = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "kim");
13. 벌크 연산 처리
- 대량의 데이터를 일일이 Update 또는 Delete 하는 방법보다 한번에 모두 쿼리하는 것이 더 효과적인 방법이다.
- 벌크 연산은 영속성 컨텍스트를 무시하고 쿼리를 실행한다.
- 벌크 연산 전 flush()가 내부적으로 호출되어, 쓰기 지연(Flush Queue)에 있던 쿼리가 실행된다.
- 벌크 연산 후에 영속성 컨텍스트와 DB 데이터 불일치 문제가 발생하므로 반드시 영속성 컨텍스트를 초기화 해주도록 하자.
// 모든 회원 나이를 +1 증가
UPDATE Member m SET m.age = m.age + 1
// 특정 조건을 만족하는 회원 삭제
DELETE FROM Member m WHERE m.age < 18
'Programming > Java' 카테고리의 다른 글
[JPA] 값 타입 정리(기본값, 임베디드, 컬렉션) (0) | 2025.01.02 |
---|---|
[JPA] CASECADE(영속성 전이) 가 무엇인가? (0) | 2024.12.23 |
[JPA] Entity 로딩 전략과 프록시(Proxy) (0) | 2024.12.23 |
[JPA] JPA에서 DB 슈퍼-서브 타입 구현하는 방법 (0) | 2024.12.20 |
[JPA] Entity 연관 관계 매핑(단방향/양방향, 연관관계주인, 다중성) (0) | 2024.12.13 |