Search
〰️

JPQL

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()