JPQL 소개
•
Java Persistence Query Language
•
가장 단순한 조회 방법
◦
EntityManager.find()
◦
객체 그래프 탐색(a.getB(), getC())
•
나이가 18살 이상인 회원을 모두 검색하고 싶다면?
JPQL을 왜 사용할까?
•
JPA를 사용하면 Entity 객체를 중심으로 개발
•
문제는 검색 쿼리
◦
예) 상위 10건만 가져오기
•
검색을 할 때도, 테이블이 아닌 엔티티 객체를 대상으로 검색
•
모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
•
애플리케이션이 필요한 데이터만 DB에서 불러오려면, 결국 검색 조건이 포함된 SQL이 필요
◦
실제 물리적인 Table을 대상으로 Query를 날리면, Table 종속적인 설계가 됨
•
JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
◦
SQL을 추상화해서 특정 DB SQL에 의존하지 않는다.
•
SQL과 문법 유사
◦
SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
•
JPQL은 엔티티 객체를 대상으로 쿼리
◦
DB 방언과 합쳐져서 자동으로 Query에 반영
•
SQL은 데이터베이스 테이블을 대상으로 쿼리
•
테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
•
SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
SQL은 객체지향 SQL이다!
JPQL 문법
기본 문법
select_문 :: =
select_절
from_절
[where_절]
[groupby_절]
[having_절]
[orderby_절]
update_문 :: = update_절 [where_절]
delete_문 :: = delete_절 [where_절]
select m from Member as m where m.age > 18;
SQL
복사
•
Entity와 속성은 대소문자 구분 ⭕️ (Member, age)
•
JPQL 키워드는 대소문자 구분
(SELECT, from, where)
•
Entity 이름 사용, 테이블 이름이 아님(Member)
•
별칭은 필수(m) (as는 생략가능)
집합과 정렬
select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from Member m
SQL
복사
•
Group by, Order by, Having 모두 SQL과 동일
TypeQuery, Query
•
TypeQuery : 반환타입이 명확할 때 사용
•
Query : 반환 타입이 명확하지 않을 때 사용
TypedQuery<Member> typedQuery = em.createQuery("select m from Member m", Member.class);
Query query = em.createQuery("select m.username, m.age from Member m");
Java
복사
결과 조회 API
•
query.getResultList()
◦
결과가 하나 이상일 때, 리스트 반환
▪
결과가 없으면 빈 리스트 반환
TypedQuery<Member> typedQuery = em.createQuery("select m from Member m", Member.class);
List<Member> resultList = typedQuery.getResultList();
for (Member member1 : resultList) {
System.out.println("member1 = " + member1);
}
Java
복사
•
query.getSingleResult()
◦
결과가 정확히 하나, 단일 객체 반환, 값이 있음이 보장될 때!!
▪
결과가 없으면 : javax.persistence.NoResultException
▪
둘 이상이면 : javax.persistence.NonUniqueResultException
TypedQuery<Member> typedQuery = em.createQuery("select m from Member m where m.username = 'danadot'", Member.class);
Member resultList = typedQuery.getSingleResult();
Java
복사
Parameter Binding
•
이름 기준
Member singleResult = em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
System.out.println("singleResult = " + singleResult.getUsername());
Java
복사
•
위치 기준 → 쓰지마세욥~! (Parameter 추가 시 버그로 이어질 가능성 높음!)
em.createQuery("SELECT m FROM Member m where m.username=?1")
.setParameter(1, usernameParam)
.getSingleResult();
Java
복사
프로젝션
•
select 절에 조회할 대상을 지정하는 것
•
DISTINCT로 중복제거
•
프로젝션 대상
◦
Entity, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입)
select m from Member m // Entity 프로젝션
select m.team from Member m // Entity 프로젝션
select m.address from Member m // 임베디드 타입 프로젝션
select m.username, m.age from member m // 스칼라 타입 프로젝션
SQL
복사
•
Entity 프로젝션
List<Member> result = em.createQuery("select m from Member m", Member.class)
.getResultList();
// join 쿼리가 나갈지 예측이 되지않기 때문에 bad case (묵시적 join)
List<Team> teamList = em.createQuery("select m.team from Member m", Team.class)
.getResultList();
// join시 SQL Query와 되도록이면 동일하게 나가는 것이 유지보수에 좋다. -> 아래와 같이 join을 명시
// join이 나가는구나~ 예측 가능 (명시적 join)
List<Team> teamList = em.createQuery("select t from Member m join m.team t", Team.class)
.getResultList();
Java
복사
•
Embedded 타입 프로젝션
◦
한계 : 소속 Entity를 정해줘야한다. (select a from address 이런식은 불가능)
em.createQuery("select o.address from Order o", Address.class)
.getResultList();
Java
복사
•
스칼라 타입 프로젝션
em.createQuery("select DISTINCT m.username, m.age from Member m")
.getResultList();
Java
복사
여러 값 조회
•
SELECT m.username, m.age FROM Member m
1.
Query 타입으로 조회
List resultList = em.createQuery("select m.username, m.age FROM Member m")
.getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) o; // type casting
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
Java
복사
2.
Object[] 타입으로 조회
List<Object[]> resultList = em.createQuery("select m.username, m.age FROM Member m")
.getResultList();
Object[] result = resultList.get(0);
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
Java
복사
3.
new 명령어로 조회
•
단순 값을 DTO로 바로 조회
•
패키지 명을 포함한 전체 클래스명 입력
•
순서와 타입이 일치하는 생성자 필요!
List<MemberDTO> resultList = em.createQuery("select new jpql.MemberDTO(m.username, m.age) FROM Member m", MemberDTO.class)
.getResultList();
MemberDTO memberDTO = resultList.get(0);
System.out.println("memberDTO.getUsername() = " + memberDTO.getUsername());
System.out.println("memberDTO.getAge() = " + memberDTO.getAge());
Java
복사
Paging API
•
JPA는 페이징을 다음 두 API로 추상화
◦
setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
◦
setMaxResult(int maxResult) : 조회할 데이터 수
List<Member> result = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
Java
복사
•
H2 Dialect
•
Oracle Dialect
조인
•
내부 조인
◦
SELECT m FROM Member m [INNER] JOIN m.team t
•
외부 조인
◦
SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
•
세타 조인 (연관관계가 없는... 막 join..)
◦
select count(m) from Member m, Team t where m.username = t.name
•
ON절을 활용한 조인(JPA 2.1부터 지원)
◦
조인 대상 필터링
▪
예) 회원과 팀을 조인하면서, 팀의 이름이 A인 팀만 조인
//JPQL
SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
//SQL
SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID = t.id and t.name = 'A'
SQL
복사
◦
연관관계 없는 엔티티 외부 조인(Hibernate 5.1부터)
▪
예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
//JPQL
SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
//SQL
SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
SQL
복사
서브쿼리
•
나이가 평균보다 많은 회원
select m from Member m
where m.age >
(select avg(m2.age)
from Member m2)
SQL
복사
•
한 건이라도 주문한 고객
select m from Member m
where (select count(o)
from Order o
where m = o.member) > 0
SQL
복사
서브쿼리 지원 함수
•
[NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참
◦
{ALL | ANY | SOME} (subquery)
◦
ALL : 모두 만족하면 참
◦
ANY, SOME : 같은 의미, 조건을 하나라도 만족하면 참
•
[NOT] IN (subquery) : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
예제
•
팀A 소속인 회원
◦
select m from Member mwhere exists (select t from m.team t where t.name = ‘팀A')
•
전체 상품 각각의 재고보다 주문량이 많은 주문들
◦
select o from Order owhere o.orderAmount > ALL (select p.stockAmount from Product p)
•
어떤 팀이든 팀에 소속된 회원
◦
select m from Member m where m.team = ANY (select t from Team t)
JPA 서브쿼리 한계
•
JPA는 WHERE, HAVING절에서만 서브 쿼리 사용 가능
•
SELECT 절도 가능 (Hibernate에서 지원)
•
FROM 절의 서브쿼리는 현재 JPQL에서 불가능
◦
JOIN으로 풀 수 있으면 풀어서 해결
JPQL 타입 표현
•
문자 : ‘HELLO’, ‘she’’s’
•
숫자: 10L(Long), 10D(Double), 10F(Float)
•
Boolean: TRUE, FALSE
•
ENUM: jpabook.MemberType.Admin (패키지명 포함)
•
엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)
◦
select i from Item i where type(i) = Book
JPQL 기타
•
SQL과 문법이 같은 식
◦
EXISTS, IN
◦
AND, OR, NOT
◦
=, >, >=, <, <=, <>
◦
BETWEEN, LIKE, IS NULL
조건식
CASE식
•
기본 Case식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
SQL
복사
•
단순 Case식
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t
SQL
복사
그외
•
coalesce : 하나씩 조회해서 null이 아니면 반환
select coalesce(m.username, '이름 없는 회원') from Member m
SQL
복사
•
nullif : 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
select nullif(m.username, '관리자') from Member m
SQL
복사
JPQL 기본 함수
•
concat
•
substring
•
trim
•
lower, upper
•
length
•
locate
•
abs, sqrt, mod
•
size, index(jpa 용도)
사용자 정의 함수 호출
•
하이버네이트는 사용전 방언에 추가해야한다.
◦
사용하는 DB방언을 상속받고, 사용자 정의 함수를 등록한다.
// H2Dialect Class 상속, 함수 등록
public class MyH2dialect extends H2Dialect {
public MyH2Dialect() {
registerFunction("group_concat, new StandardSQLFunction("group_concat", StandardBasicType.STRING));
}
}
// persistence.xml 방언 설정 변경
<property name="hibernate.dialect" value="org.hibernate.dialect.MyH2dialect"/>
// 사용
select function('group_concat', m.usernaame) from Member m
// hibernate는 아래와 같이 사용도 가능하다.
select group_concat(m.username) From Member m
Java
복사
경로표현식
•
.(점)을 찍어 객체 그래프를 탐색하는 것
select m.username // 상태필드
from Member m
join m.team t // 단일 값 연관 필드
join m.orders o // 컬렉션 값 연관 필드
where t.name = '팀A'
SQL
복사
경로표현식 용어 정리
•
상태 필드(state field) : 단순히 값을 저장하기 위한 필드
◦
예) m.username
•
연관 필드(association field) : 연관관계를 위한 필드
◦
단일 값 연관 필드
▪
@ManyToOne, @OneToOne, 대상이 엔티티(m.team)
◦
컬렉션 값 연관 필드
▪
@OneToMany, @ManyToMany, 대상이 컬렉션(m.orders)
경로 표현식 특징
•
상태 필드(state field)
◦
경로 탐색의 끝, 탐색 x
•
단일 값 연관 경로
◦
묵시적 내부 조인(inner join) 발생, 탐색 O
▪
묵시적 내부 조인이 일어나게 되면.. 쿼리 유지보수 & 튜닝이 매우 어려워진다. (찾기가 어려움ㅠ)
select m.team.name From Member m
SQL
복사
•
컬렉션 값 연관 경로
◦
묵시적 내부 조인 발생, 탐색X
▪
FROM절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색 가능
→ 묵시적 조인이 발생하는 경우는 사용하지말자!
명시적 조인과 묵시적 조인
•
명시적 조인 : join 키워드 직접 사용
◦
select m from Member m join m.team t
•
묵시적 조인 : 경로 표현식에 의해 묵시적으로 SQL 조인 발생 (내부조인만 가능)
◦
select m.team from Member m
select o.member.team from Order o → 성공
select t.members from Team → 성공
select t.members.username from Team t → 실패
select m.username from Team t join t.members m → 성공
경로 탐색을 사용한 묵시적 조인 시 주의사항
•
항상 내부조인
•
컬렉션은 경로 탐색의 끝, 명시적 조인을 통해 별칭을 얻어야함
•
경로 탐색은 주로 select, where절에서 사용하지만, 묵시적 조인으로 인해 SQL FROM절에 영향을 준다.
가급적 묵시적 조인 대신에 명시적 조인 사용
조인은 SQL튜닝에 중요한 포인트이므로 DB Dependency를 가져간다.
묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어려움
Fetch Join
️
️
️
️
️
•
SQL Join의 종류가 아니다.
•
JPQL에서 성능 최적화를 위해 제공하는 기능
•
연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
•
join fetch 명령어 사용
Entity Fetch Join
•
회원을 조회하면서 연관된 팀도 함께 조회 (SQL 한 번에)
•
SQL을 보면 회원뿐만 아니라 팀(T.*)도 함께 Select
// 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
SQL
복사
Team teamA = new Team();
teamA.setName("teamA");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("teamB");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setAge(10);
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setAge(10);
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setAge(10);
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
Java
복사
String query = "select m From Member m";
List<Member> resultList = em.createQuery(query, Member.class)
.getResultList();
for (Member m : resultList) {
// proxy객체 생성, m.getTeam().getName()이 실행될 때 초기화 진행
System.out.println("m = " + m.getUsername() + ", " + m.getTeam().getName());
// 회원1,팀A (SQL)
// 회원2,팀A (1차 캐시에서 가져옴)
// 회원3,팀B (SQL)
// 회원 100명 -> 1(회원) + N
}
Java
복사
→ N+1 문제가 발생하게 된다.
// fetch join 사용
String query = "select m From Member m join fetch m.team";
List<Member> resultList = em.createQuery(query, Member.class)
.getResultList();
for (Member m : resultList) {
// 영속성 context에 member와 team의 data가 불러와짐. team객체도 proxy가 아니다!
System.out.println("m = " + m.getUsername() + ", " + m.getTeam().getName());
}
Java
복사
→ 한방에 불러오자!
컬렉션 페치 조인
•
일대다 관계, 컬렉션 페치 조인
// JPQL
select t from Team t join fetch t.members where t.name = '팀A'
// SQL
select t.*, m.* from team t inner join member m on t.id = m.team_id where t.name = '팀A'
SQL
복사
Fetch Join과 DISTINCT
•
SQL의 DISTINCT는 중복된 결과를 제거
•
JPQL의 DISTINCT는 2가지 기능 제공
◦
SQL에 DISTINCT를 추가
◦
에플리케이션에서 엔티티 중복 제거
•
SQL에서
◦
select distinct t from Team t join fetch t.members
where t.name = ‘팀A’
▪
SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과
에서 중복제거 실패 (데이터가 완전히 동일한 값이여야 중복이 제거된다)
•
JPQL에서
◦
DISTINCT가 추가로 애플리케이션에서 중복 제거시도
▪
같은 식별자를 가진 Team 엔티티 제거
Fetch Join과 일반 Join의 차이
// JPQL
select t
from Team t join t.members m where t.name = '팀A'
// SQL
SELECT T.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID WHERE T.NAME = '팀A'
SQL
복사
•
일반 조인 실행시 연관된 엔티티를 함께 조회하지 않음
◦
JPQL은 결과를 반환할 때 연관관계를 고려하지 않는다.
◦
단지 SELECT절에 지정한 엔티티만 조회한다.
◦
여기서는 팀 엔티티만 조회하고, 회원 엔티티는 조회하지 않음.
•
페치조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
◦
페치조인은 객체 그래프를 SQL 한번에 조회하는 개념
Fetch Join의 특징과 한계
•
페치조인 대상에는 별칭을 줄 수 없다.
◦
하이버네이트는 가능, 가급적 사용X
•
둘 이상의 컬렉션은 페치조인 할 수 없다.
◦
데이터 정합성의 문제가 발생할 수 있음. 1:N도 N+1의 문제가 생김.
◦
1:N:N ..!?!!
◦
컬렉션은 단 하나만 페치조인 할 수 있다고 기억하자.
•
컬렉션을 페치조인하면 페이징 API(setFirstresult, setMaxResults)를 사용할 수 없다.
◦
일대일, 다대일 같은 연관 필드들은 페치 조인해도 페이징 가능
◦
하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험) → 절대 사용 금지!!!!!!!!!
•
연관된 엔티티들을 SQL 한 번으로 조회 → 성능 최적화
•
엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선
◦
글로벌 로딩 전략 : @OneToMany(fetch = FetchType.LAZY)
•
실무에서 글로벌 로딩 전략은 모두 지연로딩
•
최적화가 필요한 곳은 페치조인 적용
# 실무에서는 Batch Fetch size를 global option으로 주고 사용
# Query가 N+1이 아니라 table 사이즈만큼 맞출 수 있음!
hibernate.default_batch_fetch_size: 100
YAML
복사
정리
•
모든 것을 페치 조인으로 해결할 수는 없음!
◦
N+1 문제는 대부분 fetch join으로 잡을 수 있음~~~! JPA문제의 70~80%
•
페치조인은 객체 그래프를 유지할 때 사용하면 효과적
•
여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야하면, 페치 조인보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적!
다형성 쿼리
•
조회 대상을 특정 자식으로 한정
◦
예) ITEM중에 Book, Movie를 조회해라
-- JPQL
select i from i where type(i) IN (Book, Movie)
-- SQL
select i from i where i.dtype in ('B', 'M')
SQL
복사
•
TREAT(JPA2.1)
◦
예) 부모인 Item과 자식 Book이 있다.
-- JPQL
select i from Item i where treat(i as Book).author = 'kim'
-- SQL
select i.* from item i where i.DTYPE = 'b' and i.auther = 'kim'
SQL
복사
엔티티 직접 사용
기본키 값
•
JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용
-- JPQL
select count(m.id) from Member m //엔티티의 아이디를 사용
select count(m) from Member m // 엔티티를 직접 사용
-- SQL
select count(m.id) as cnt from Member m // 기본 키 값을
SQL
복사
•
엔티티를 파라미터로 전달
String jpql = "select m from Member m where m = :member";
List resultList = em.createQuery(jpql)
.setParameter("member", member)
.getResultList();
Java
복사
•
식별자를 직접 전달
String jpql = "select m from Member m where m.id = :memberId";
List resultList = em.createQuery(jpql)
.setParameter("memberId", memberId)
.getResultList();
Java
복사
→ 실행된 SQL
select m.from Member m where m.id=?
SQL
복사
Named Query (어노테이션)
@Entity
@NamedQuery(
name = "Member.findByUsername", // findByUsername도 가능. 관례상 entity명+
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();
Java
복사
•
Named 쿼리 - 정적쿼리
◦
미리 정의해서 이름을 부여해두고 사용하는 JPQL
◦
정적쿼리만 사용 가능!
◦
어노테이션, XML에 정의 가능
◦
애플리케이션 로딩 시점에 초기화 후 재사용
◦
애플리케이션 로딩 시점에 쿼리를 검증
▪
JPA가 Parsing을 해주기 때문에 compile시에 잡힌다!
벌크 연산
•
재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
•
JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL 실행
◦
재고가 10개 미만인 상품을 리스트로 조회한다.
◦
상품 엔티티의 가격을 10% 증가한다.
◦
트랜잭션 커밋 시점에 변경감지가 동작한다.
•
변경된 데이터가 100건이라면 100번의 UPDATE SQL 실행
◦
JPA는 벌크성보다, 실시간성에 초점
•
한 번에 여러 row를 update, delete 할 때! → 벌크연산 사용
예제
•
쿼리 한 번으로 여러 테이블 로우 변경(Entity)
•
executeUpdate()의 결과는 영향받은 엔티티 수 반환
•
UPDATE, DELETE 지원
•
INSERT(insert into ... select, 하이버네이트 지원)
String qlString = "update Product p " +
"set p.price = p.price * 1.1 " +
"where p.stockAmount < :stockAmount";
// FLUSH 자동 호출
int resultCount = em.createQuery(qlString)
.setParameter("stockAmount", 10)
.executeUpdate();
Java
복사
주의
•
벌크 연산은 영속성 컨텍스트를 무시하고, 데이터베이스에 직접 쿼리
//벌크연산은 영속성 컨텍스트를 무시한다
em.createQuery("update Member m set m.age = 20")
.executeUpdate();
//벌크연산 후에는 영속성 컨텍스트를 초기화해주는 것이좋다.
//영속성 컨텍스트가 초기화 되었기때문에 DB에서 값을 가져오고 영속성 컨텍스트에 다시 올릴 것이다.
// em.clear();
// em.flush();
//DB에는 update 되었지만 조회시에는 영속성컨텍스트에 저장된 값을 가져오게 된다.
Member member = em.find(Member.class, member1.getId());
System.out.println("member.getAge() = " + member.getAge());
Java
복사
◦
벌크 연산을 먼저 실행하거나, 벌크 연산 수행 후 영속성 컨텍스트를 초기화해라!
→ 영속성 컨텍스트와 벌크연산 사이에 정합성 문제가 생길 수 있으므로...!!
벌크 연산 사용시에는,
1) 벌크 연산을 먼저 수행
→ 가장 실용적. 조회를 하기 전 벌크 연산을 먼저 수행한 후 엔티티를 조회하면 문제가 없다.
2) 벌크 연산 수행후 영속성 컨텍스트 초기화
→ 벌크 연산 전에 영속성 컨텍스트에 올려져있던 엔티티들을 영속성 컨텍스에서 제거한다.
둘 중 자신의 상황에 맞는 가이드를 사용해서 이슈를 피하도록 하자.
참고
Criteria 소개
// Criteria 사용 준비, java 표준 문법
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
// 루트 클래스 (조회를 시작할 클래스)
Root<Member> m = query.from(Member.class);
// 쿼리 생성
CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("username"), "danadot"));
List<Member> resultList = em.createQuery(cq)
.getResultList();
Java
복사
•
문자가 아닌 자바코드로 JPQL을 작성할 수 있음
•
JPQL 빌더 역할
•
JPA 공식 기능
•
BUT 너무 복잡하고 실용성이 없다. 실무에서는 사용하지 않는다
◦
왜? 유지보수가 어렵다...ㅎㅎ..ㅎ
•
Criteria 대신에 QueryDSL 사용 권장
QueryDSL 소개
//JPQL
//select m from Member m where m.age > 18
JPAFactoryQuery query = new JPAQueryFactory(em); QMember m = QMember.member;
List<Member> list = query.selectFrom(m)
.where(m.age.gt(18)) .orderBy(m.name.desc()) .fetch();
Java
복사
•
문자가 아닌 자바코드로 JPQL을 작성할 수 있음
•
JPQL 빌더 역할
•
컴파일 시점에 문법 오류를 찾을 수 있음
•
동적쿼리 작성 편리함
•
단순하고 쉬움 → 실무 사용 권장
Native SQL 소개
•
JPA가 사용하는 SQL을 직접 사용하는 기능
String sql =
"SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = 'danadot'";
List<Member> resultList =
em.createNativeQuery(sql, Member.class).getResultList();
Java
복사
•
JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능
◦
예) 오라클 Connect By ..
JDBC 직접 사용, SpringJdbcTemplate 등..
•
JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나, 스프링 JdbcTemplate, Mybatis등을 함께 사용 가능
•
단, 영속성 컨텍스트를 적절한 시점에 강제로 flush() 필요
◦
예) JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트 수동 flush()