본문 바로가기
Jpa

객체 지향 쿼리 언어

by k0o9 2021. 1. 13.

참고 이글은 자바 ORM 표준 JPA프로그래밍을 읽고 정리한 글입니다.

Jpa는 복잡한 검색 조건을 사용해서 엔티티 객체를 조회할 수 있는 다양한 쿼리 기술을 지원한다. 

 

가장 단순한 검색 방법

식별자로 조회 : EntityManager.find()

객체 그래프 탐색 : a.getB().getC()

 

이 기능들만으로는 애플리케이션 개발이 어려움 -> 복잡한 조건 검색 필요.

ORM을 사용하면 DB테이블이 아닌 엔티티 객체를 대상으로 개발하므로 검색도 테이블이 아닌 엔티티 객체를 대상으로 하는 방법이 필요함.

 

위의 이러한 문제로 만들어진게 JPQL이다.

 

JPQL 특징

: 테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리.

: SQL을 추상화해서 특정 DB SQL에 의존하지 않는다.(데이터 방언설정)

 

-SQL과의 차이점

 

JPQL vs SQL

SQL : DB테이블 대상으로하는 데이터 중심 쿼리.

 

JPQL : 엔티티 객체를 대상으로 하는 객체지향 쿼리.

 

JPQL을 사용하면 JPA는 JPQL을 분석해서 알맞은 SQL쿼리문을 만들어 DB에 조회하고 받은 결과를 반환한다.

 

JPQL은 SQL에 비해 간결하다 -> 엔티티 직접 조회, 묵시적 조인, 다형성 지원

 

Criteria 쿼리

JPQL을 생성하는 빌더 클래스로 문자가 아닌 query.select.where(...)같은 프로그래밍 코드로 JPQL을 작성 할 수 있게 해준다.

 

JPQL:문자

Criteria : 코드

 

Criteria 장점

: 컴파일 시점에 오류 발견할 수 있다.

: IDE를 사용하면 코드 자동완성을 지원한다.

: 동적 쿼리를 작성하기 편하다.

 

매개변수 필드명은 문자로 작성하는데 이러한 문자도 코드로 작성하고 싶으면 메타 모델 사용.

 

Criteria 단점

모든 장점을 상쇄할 정도로 복잡하고 장황하다.

-> Criteria 보다 QueryDSL 사용 추천

 

QueryDSL

Criteria처럼 JPQL의 빌더역할을 한다. Criteria에 비해 코드 기반이면서도 단순하고 사용하기 쉽다. 또한 문법적으로 JPQL과 비슷해서 눈에도 잘들어온다.

 

어노테이션 프로세서를 이용해서 쿼리 전용 클래스를 만들어야 함.

 

네이티브 SQL

JPQL도 특정 DB에서 만큼은 DB에 의존해야 할 때가 있다. 예를 들어 오라클 DB만 사용하는 CONNECT BY 기능이나 특정 DB에서만 동작하는 SQL 힌트같은 것들이 있다. 이런경우에는 JPQL을 사용하지 못한다. 그래서 SQL을 직접 사용할 수 있는 기능을 JPA에서는 지원을 해주는데 이것을 네이티브 SQL이라 한다.

 

단점

: DB에 의존하는 SQL문 사용.

 

JDBC 직접 사용

JPA는 JDBC커넥션을 획득하는 API를 제공하지 않기 때문에 JDBC 커넥션에 접근하고 싶으면 JPA 구현체가 제공하는 방법을 사용해야 한다.

 

-방법

1.JPA EntityManager에서 하이버네이트 Session을 구한다

2.Session의 doWork()메소드를 호출하면 된다.

 

-주의

JDBC나 마이바티스를 JPA와 함께 사용하면 영속성 컨텍스트를 적잘함 시점에 강제로 플러시 해야한다.

ㄴ JDBC나 마이바티스같은 SQL매퍼는 JPA를 우회해서 DB에 접근하기에 JPA는 인지하지 못한다.

 

스프링을 사용할수 jpa와 마이바티스를 손쉽게 통합가능.

 

JPQL

위에 설명한 어떤 방법을 사용하든 결국 모두 JPQL을 사용한다.

 

자 여기서 Address는 임베디드 타입인데 이것은 값타입이므로 UML에서 스테레오 타입을 사용해 <Value>로 정의했다.

 

JPQL은 엔티티를 저장할때 persist()메소드를 사용하므로 따로 INSERT문은 없다.

 

-JPQL SELECT 문법 특징

엔티티와 속성은 대소문자를 구분해야한다.

JPQL 키워드는 대소문자를 구분하지 않는다.(SELECT FROM)

엔티티명을 받아와서 사용한다. 엔티티명이 지정안되있으면 클래스명을 기본값으로 한다.

별칭은 필수이다. AS를 사용해서 꼭 별칭을 지정해줘야 한다.

 

TypeQuery Query

쿼리 객체에는 2가지 종류가 있다.

TypeQuery:반환할 타입이 지정되있을 경우

Query:반환할 타입x

 

여러 엔티티나 컬럼을 선택할경우 반환타입이 명확하지 않으면 Query를 사용한다.

 

결과 조회 API

query.getResultList()결과를 반환, 결과가 없으면 빈 컬렉션을 반환한다.

query.getSingelResult():결과가 정확히 하나 일 때 사용 -> 없거나 많으면 에러.

 

파라미터 바인딩

 

JDBC는 위치 기준 파라미터 바인딩만 지원하지만 JPQL은 이름 기준 파라미터 바인딩도 지원한다.

 

이름 기준 파라미터

파라미터를 이름으로 구분하는 방법. 이름기준 파라미터는 앞에 : 을 사용.

 

String usernameParam="User"

SELECT m FROM Member m where m.username =:username

 

query.setParameter("username",usernameParam)

 

위치 기준 파라미터

?다음에 위치 값을 주면 된다. 위치 값은 1부터 시작.

String usernameParam="User"

SELECT m FROM Member m where m.username=?1

query.setParameter(1, usernameParam);

 

이름기준 파라미터 바인딩 방식을 사용하는 것이 더 명확

 

프로젝션

Select절에 조회할 대상을 지정. SELECT 프로젝션 대상(엔티티,엠비디드 타입,스칼라 등) FROM 으로 대상을 지정한다.

 

- SELECT m FROM Member m -> 엔티티 프로젝션

- SELECT m.team FROM Member m -> 엔티티 프로젝션

원하는 객체를 바로 조회한 것. 컬럼을 하나하나 나열해서 조회해야하는 SQL과 차이

조회한 엔티티는 영속성 컨텍스트에서 관리.

 

- SELECT m.address FROM Member m -> 임베디드 타입 프로젝션

임베디드 타입은 조회의 시작점이 될수 없다.

영속성 컨텍스트에서 관리되지 않음

 

- SELECT m.username, m.age FROM Member m -> 스칼라 타입 프로젝션

 

- DISTINCT로 중복 제거 

 

여러 값 조회

프로젝션에 여러 값을 동시에 조회할 경우에는 Query에 받아서 사용.

->Object[]로 하나하나 객체 변환. -> 변환해야하는 번거로운 문제점 발생

 

New 명령어

SELECT뒤에 NEW명령어를 사용하면 반환받을 클래스를 지정할 수 있는데 이 클래스의 생성자에 JPQL조회 결과를 넘겨 줄 수 있다. 그리고 NEW 명령어를 사용한 클래스는 TYPEQUERY로 받을수 있어서 변환작업을 줄일수 있다.

 

-주의점

: 패키지 명을 포함한 전체 클래스명 입력

: 순서와 타입이 일치하는 생성자 필요.

 

페이징 API

DB마다 페이징을 처리하는 SQL문법이 다름. 그래서 JPA는 페이징을 두 API로 추상화함.

 

1.setFirstResult(int startPosition) : 조회 시작 위치

2.setMaxResults(int maxResult) : 조회할 데이터 수

 

String jpql = "select m from Member m order by m.name desc";
 List<Member> resultList = em.createQuery(jpql, Member.class)
 .setFirstResult(10)
 .setMaxResults(20)
 .getResultList();

11번째 위치에서부터 총 20건의 데이터 조회.

 

이렇게 DB마다 다른 페이징처리를 API로 처리할 수 있는 것은 데이터 베이스 방언 덕분.

페이징 API를 더 최적화 하고 싶으면 네이티브 SQL로 직접 작성

 

집합과 정렬

집합 : 집합함수와 함께 통계 정보를 구할 때 사용.

select
 COUNT(m), //회원수
 SUM(m.age), //나이 합
 AVG(m.age), //평균 나이
 MAX(m.age), //최대 나이
 MIN(m.age) //최소 나이
from Member m

 

-참고사항

: NULL값은 무시하므로 통계에 잡히지 않음

: 값이 없는데 집합 함수 사용시 NULL값이 됨. COUNT의 경우에는 0

: DISTINCT를 집합함수 안에 사용해서 중복 값 제거하고 나서 집합을 구할 수 있음.

: DISTINCT를 COUNT에서 사용할 때 임베디드 타입은 지원X 

 

GROUP BY, HAVING

GROUP BY 는 통계 데이터를 구할 때 특정 그룹끼리 묶어준다.

HAVING은 GROUP BY와 함께 사용하는데 그룹화한 통계데이터를 기준으로 필터링한다.

 

보통 이러한 쿼리들을 리포팅 쿼리나 통계쿼리라고 부름.

 

통계 쿼리는 보통 전체 데이터를 기준으로 처리하므로 실시간으로 사용하기에 부담이 많다. 결과가 아주 많다면 통계 결과만 저장하는 테이블을 별도로 만들어 두고 사용자가 적은 새벽에 통계 쿼리를 실행해서 그 결과를 보관하는 것이 좋다.

 

정렬(ORDER BY)

결과들을 정렬 DESC,ASC로 내림차순 오름차순.

 

JPQL 조인

SQL 조인과 기능은 같고 문법적으로 다름.

 

내부 조인

INNER JOIN사용. 

SELECT m FROM Member m [INNER] JOIN m.team t

JQPL의 가장 큰 특징은 연관필드를 사용. SQL과 달리 JOIN명령어 다음에 조인할 객체의 연관필드를 사용해야함.

 

외부 조인

기능상 SQL의 외부조인과 같음. OUTER 생략 가능.

SELECT m FROM Member m LEFT [OUTER] JOIN m.team t 

 

컬렉션 조인

일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하는 것.

 

다대일 : 단일 값 연관 필드 사용

일대다 : 컬렉션값 연관필드 사용.

 

세타 조인

WHERE절을 사용해서 세타 조인 가능.

세타조인은 내부 조인만 지원한다.

전혀 관계없는 엔티티도 조인 가능.

select count(m) from Member m, Team t where m.username= t.name
Join On

On절을 사용해 조인대상을 필터링하고 조인할 수 있다. 내부 조인의 on절은 where과 같으므로 보통 외부 조인일때만 사용.

 

페치 조인

조인의 종류X , JPQL에서 성능 최적화를 위해 제공.

연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능.

join fetch명령어로 사용 가능.

페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로

 

엔티티 패치 조인

일반적 JQPL조인과는 다르게 별칭을 사용할 수 없다.

(하이버네이트는 패치 조인에도 별칭을 허용한다.)

 

페치 조인

페치 조인은 SQL문 한번에 연관된 엔티티나 컬렉션을 조회한다. -> 사진을 보면 MEMBER를 조회하면 TEAM도 같이 조회.

//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 

그림을 보면 회원과 팀 객체가 객체 그래프를 유지하면서 조회된 것을 확인 가능.

String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class)
 .getResultList();
for (Member member : members) {
 //페치 조인으로 회원과 팀을 함께 조회해서 지연 로딩X
 System.out.println("username = " + member.getUsername() + ", " +
 "teamName = " + member.getTeam().name());
} 

//출력결과
username=회원1, teamname=팀A
username=회원2, teamname=팀B
username=회원3, teamname=팀C

 

회원과 팀을 지연로딩으로 설정했다고 가정하면 회원과 연관된 템엔티티는 프록시가 아닌 실제 엔티티이다. 따라서 연관된 팀을 사용해도 지연로딩이 일어나지 않는다. 또한 회원 엔티티가 영속성 컨텍스트에서 분리되어 준영속 상태가 되어도 연관된 팀을 조회 할 수 있다. 

 

컬렉션 패치 조인

-일대다 컬렉션

//[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' 

컬렉션 패치조인

 

패치 조인과 DISTINCT

SQL의 DISTINCT는 중복된 결과를 제거하는 명령어.

JPQL의 DISTINCT는 SQL의 DISTINCT+ 애플리케이션에서 한번 더 중복 제거.

위의 데이터를 사용하겠다.

select distinct t
from Team t join fetch t.members
where t.name = ‘팀A'

jpql에 distinct를 추가하면 먼저 SQL에 SELECT DISTINCT가 추가된다. 그러나 각 로우의 데이터가 달라 효과가 없다. 그 후 애플리케이션에서 DISTINCT를 보고 중복된 데이터를 걸러낸다. select distinct t 의 의미는 팀 엔티티의 중복을 제거하라는 것이다. 따라서 중복인 팀은 하나만 조회된다.

패치조인 distinct

패치 조인과 일반조인의 차이

일반조인 시 연관된 엔티티를 함께 조회하지 않는다. 

JPQL은 결과를 반환할 때 연관관계를 고려하지 않고 단지 SELECT절에 지정한 엔티티만 조회한다.

지연로딩으로 설정시 패치조인은 실제 엔티티를 반환햇지만 일반조인은 초기화되지 않은 컬렉션 래퍼를 반환한다.

페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념

 

페치 조인의 특징과 한계

-특징

: 페치조인 사용시 SQL 한번으로 연관된 엔티티들을 함께 조회해 SQL 호출횟수를 줄여서 성능 최적화.

 

*글로벌 로딩전략 : 엔티티에 직접 적용하는 로딩전략으로 애플리케이션 전체에 영향을 미침.

: 페치 조인은 글로벌 로딩 전략보다 우선한다.

 

->될수 있으면 글로벌 로딩 전략은 지연로딩으로하고 최적화가 필요하면 패치조인 사용

: 연관된 엔티티를 쿼리시점에 조회하므로 지연로딩 발생 X -> 준영속 상태에서 객체 그래프 탐색.

 

-한계

: 대상에게 별칭을 줄 수 없다.

: 둘 이상의 컬렉션을 페치할 수 없다.

: 페이징 API사용불가

 

 

경로 표현식

.을 찍어 객체 그래프를 탐색하는 방법.

 

용어 정리

상태 필드 : 단순히 값을 저장하기 위한 필드

연관 필드 : 연관관계를 위한 필드, 임베디드 타입 포함

ㄴ 단일 값 연관 필드: 대상이 엔티티

ㄴ 컬렉션 값 연관 필드 : 대상이 컬렉션

 

특징

-상태 필드 경로 : 경로 탐색의 끝.

-단일 값 연관 경로: 묵시적으로 내부조인이 일어난다. (계속 탐색가능)

-컬렉션 값 연관 경로: 묵시적으로 내부조인이 일어난다. 계속 탐색할 수 없다. 그러나 FROM절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색할 수 있다.

 

상태 필드 경로 탐색

-JPQL

select m.username, m.age from Member m

-SQL

select m.username, m.age

from Member m

 

단일 값 연관 경로 탐색

-JPQL

select o.member from Order o

-SQL

select m.*

from Orders o

inner join Member m on o.member_id = m.id

 

JPQL을 보면 단일값 연관 필드로 경로 탐색을 했다. 단일값 연관필드로 경로 탐색을 하면 SQL에서 내부 조인이 일어난다 이것을 묵시적 조인이라고 함.

 

-명시적 조인:JOIN을 직접 적어주는 것.

select m from Member m join m.team t

 

-묵시적 조인: 경로 표현식에 의해 묵시적으로 조인이 일어나는 것. 내부 조인 INNER JOIN만 할 수 있다.

select m.team from Member m

 

 

컬렉션 값 연관 경로 탐색

컬렉션 값에서 경로탐색 시도하면 안됨.

 

select t.members from Team -> 성공

select t.members.username from Team t -> 실패

 

t.members처럼 컬렉션까지는 경로 탐색이 가능하다. 하지만 members.~~으로는 경로탐색을 허용하지 않는다. 

탐색을 원한다면 

select m.username from Team t join t.members m -> 성공

 

이런식으로 별칭을 얻어줘야 한다.

 

-경로 탐색을 사용한 묵시적 조인 시 주의 사항

 

:항상 내부 조인

 

: 컬렉션은 경로 탐색의 끝, 명시적 조인을 통해 별칭을 얻어야 함.

 

:경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM절에 영향을 줌.

 

서브 쿼리

JPQL도 SQL처럼 서브 쿼리 지원.

단 WHERE HAVING 절에만 사용 가능. SELECT,FROM에서는 사용 불가.

다음과 같이 사용가능

-[NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참

select m from Member m where exists (select t from m.team t where t.name = ‘팀A')

 

-{ALL | ANY | SOME} (subquery)

ALL 모두 만족하면 참

ANY, SOME: 같은 의미, 조건을 하나라도 만족하면 참

select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)

 

-[NOT] IN (subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참

select m from Member m where m.team = ANY (select t from Team t)

 

서브 쿼리 한계

JPA는 WHERE,HAVIG에서만 서브쿼리 사용 가능

SELECT절도 가능

FROM 절의 서브 쿼리는 현재 JPQL에서 불가능

조인으로 풀 수 있으면 풀어서 해결

 

조건식

-문자

작은 따옴표 사이에 표현

‘HELLO’, ‘She’’s’

 

-숫자

10L(Long), 10D(Double), 10F(Float)

 

-날짜

DATE,TIEM,DATETIME으로 표현

 

-Boolean

TRUE, FALSE

 

-ENUM

패키지명 포함한 전체 이름을 사용.

jpabook.MemberType.Admin (패키지명 포함)

 

-엔티티 타입

엔티티 타입을 표현

TYPE(m) = Member (상속 관계에서 사용)

 

연산자 우선 순위

1.경로 탐색 연산(.)

2.수학 연산: + - * / %

3.비교 연산: =,>,>=, <>,<,<=

4.논리 연산 : NOT,AND,OR

 

Between IN Like NULL 비교

-Between

x [NOT] BETWEEN A AND B

X는 A~B사이의 값이면 참 A<=X<=B

 

-IN

x [NOT] IN 예제

X와 같은 값이 예제에 하나라도 있으면 참

 

-Like

문자표현식 [Not] LIKE 패턴 값 [ESCAPE]

문자표현식과 패턴 값을 비교한다

 

-NULL

{단일값 경로 | 입력 파라미터} is [NOT] NULL

NULL인지 비교 한다 = NULL 을 사용하게는 아니라 꼭 IS NULL로 비교

 

컬렉션 식

컬렉션에서만 사용하는 특별한 기능.

컬렉션은 컬렉션 식만 사용가능. (IS NULL 같은거 사용X)

 

-빈 컬렉션식

{컬렉션 값 연관 경로} IS [NOT] EMPTY

컬렉션에 값이 비었으면 참.

 

-컬렉션의 멤버 식

{엔티티나 값}[NOT] MEMBER [OF] {컬렉션 값 연관 경로}

엔티티나 값이 컬렉션에 포함되어 있으면 참

 

스칼라 식

스칼라는 숫자 문자 날짜 case 엔티티 타입같은 가장 기본적인 타입을 말한다.

 

-수학식

+ - 단항연산자

* / + - 사칙연산

 

-문자함수

CONCAT

문자를 합한다

 

SUBSTRING(문자,위치 길이)

위치부터 시작해서 길이만큼 문자를 구한다.

 

TRIM([LEADING|TRAILING|BOTH) [트림문자] FROM]문자)

LEADING : 왼쪽만 

TRAILING : 오른쪽만

BOTH : 양쪽

기본값은 BOTH이다. 트림문자의 기본값은 공백이다.

 

LOWER(문자)

소문자로 변경

 

UPPER(문자)

대문자로 변경

 

LENGTH(문자)

문자 길이

 

LOCATE(찾을 문자,원본 문자, [검색 시작위치])

검색 위치부터 문자를 검색한다. 1부터 시작 못찾으면 0반환

 

-수학함수

ABS(수학식) 절대값을 구함

SQRT(수학식) 제곱근을 구함

MOD(수학식,나머지) 나머지를 구함

SIZE(컬렉션 값 연관 경로식) 컬렉션의 크기를 구한다.

INDEX(별칭) LIST 타입 컬렉션의 위치를 구함.

 

CASE 식

특정 조건에 딸 ㅏ분기할 때 CASE식 사용.

 

-기본 CASE

select
 case when m.age <= 10 then '학생요금'
 when m.age >= 60 then '경로요금'
 else '일반요금'
 end
from Member m

 

-심플 CASE 

조건식을 사용X 문법이 단순하다. 자바의switch 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 반환. 다르면 첫번째 값 반환.

select NULLIF(m.username, '관리자') from Member m
다형성 쿼리

JPQL로 부모 엔티티를 조회하면 자식 엔티티도 함께 조회한다.

-TYPE

엔티티의 상속 구조에서 조회 대상을 특정 자식 타입으로 한정할 때 주로 사용.

//[JPQL]
select i from Item i
where type(i) IN (Book, Movie)
//[SQL]
select i from i
where i.DTYPE in (‘B’, ‘M’)

 

-TREAT

자바의 타입 캐스팅과 비슷. 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용.

FROM WHERE SELECT 사용

[JPQL]
select i from Item i
where treat(i as Book).auther = ‘kim’
[SQL]
select i.* from Item i
where i.DTYPE = ‘B’ and i.auther = ‘kim
사용자 정의 함수 호출

하이버네이트 구현체를 사용하면 방언 클래스를 상속해서 구현하고 사용할 DB함수를 미리 등록해야 함.(사용전 방언 미리 등록)

select function('group_concat', i.name) from Item i

 

기타 정리

enum = 비교 연산만 지원.

임베디드 타입은 비교를 지원하지 않음

 

-EMPTY STRING

JPA 표준은 길이 0을 EMPTY STRING으로 정했지만 DB에 따라 ""를 NULL로 사용한느 DB도 있으니 확인하고 사용.

 

-NULL정의

조건을 만족한느 데이터가 하나도 없으면 NULL이다.

NULL은 알수 없는 값이다.

NULL==NULL은 알수 없는 값이다.

NULL is NULL 은 참이다.

 

엔티티 직접 사용

엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용.

 

[JPQL]
select count(m.id) from Member m 
select count(m) from Member m 
[SQL] // 둘다 같은 SQL로 실행
select count(m.id) as cnt from Member m
//엔티티 파라미터로 직접 받기
String jpql = “select m from Member m where m = :member”;
List resultList = em.createQuery(jpql)
 .setParameter("member", member)
 .getResultList();
select m.* from Member m where m.id=? 

where m = :member 로 엔티티를 직접 사용 하는 부분이 SQL에서 where m.id=?로 기본키 값을 사용하도록 변환되었습니다.

//식별자를 직접 전달
String jpql =select m from Member m where m.id = :memberId”;
List resultList = em.createQuery(jpql)
 .setParameter("memberId", memberId)
 .getResultList(); 

 

식별자를 직접 전달해도 똑같은 sql문으로 실행된다.

 

외래 키 값
//외래키 대신에 엔티티를 직접 사용
Team team = em.find(Team.class, 1L);
String qlString =select m from Member m where m.team = :team”;
List resultList = em.createQuery(qlString)
 .setParameter("team", team)
 .getResultList(); 

기본키 값이 1L인 팀 엔티티를 파라미터로 사용하고 있다. m.team은 현재 team_id라는 외래키와 매핑

select m.* from Member m where m.team_id=?
String qlString =select m from Member m where m.team.id = :teamId”;
List resultList = em.createQuery(qlString)
 .setParameter("teamId", teamId)
 .getResultList();

외래 키에 식별자를 직접 사용하면 위와 같은 코드가 되며 sql문은 똑같이 변환된다.

m.team.id를  를 보면 Member와 Team간에 묵시적 조인이 일어 날 것 같지만 MEMBER테이블이 team_id 외래 키를 가지고 있으므로 묵시적 조인은 일어나지 않는다. 물론 m.team.name을  을 호출하면 묵시적 조인이 일어난다.

 

Named 쿼리

JPQL쿼리는 크게 동적 쿼리와 정적 쿼리로 나눌 수 있다.

 

-동적쿼리

:JPQL을 문자로 완성해서 직접 넘기는 것.

 

-정적 쿼리

미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용할 수 있는데 이것을 Named 쿼리라 한다. Named 쿼리는 한번 정의하면 바꿀 수 없어서 정적인 쿼리이다.

 

-특징

 

미리 정의해서 이름을 부여해두고 사용하는 JPQL

 

정적 쿼리

 

어노테이션,XML에정의

 

애플리케이션 로딩 시점에 초기화 후 재사용

 

애플리케이션 로딩 시점에 쿼리를 검증

 

 

//NamedQuary 어노테이션으로 정의
@Entity
@NamedQuery(
 name = "Member.findByUsername",
 query="select m from Member m where m.username = :username")
public class Member {
 ...
}

 

@NamedQuary.name에 쿼리 이름을 부여하고 @NamedQuary..query에 사용할 쿼리 입력

List<Member> resultList =
 em.createNamedQuery("Member.findByUsername", Member.class)
 .setParameter("username",
"회원1")
 .getResultList();

이렇게 사용 가능.

 

하나의 엔티티에 2개 이상의 Named Query 를 정의하려면 @NamedQuaries어노테이션 사용.

 

Named 쿼리 XML에 정의

JPA에서 어노테이션으로 작성 할 수 있는 것은 XML로도 작성 가능하다. 물론 어노테이션이 더 직관적이고 편리하지만 Name 쿼리를 작성할때는 XML을 사용하는 것이 더 편리하다.

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
 <named-query name="Member.findByUsername">
 <query><![CDATA[
 select m
 from Member m
 where m.username = :username
 ]]></query>
 </named-query>
 <named-query name="Member.count">
 <query>select count(m) from Member m</query>
 </named-query>
</entity-mappings>

 

정의한 XML을 인식하기 위해서는 META-INF/persistence.xml에 코드르 추가해줘야 한다.

<persistence-unit name="jpabook" >
 <mapping-file>META-INF/ormMember.xml</mapping-file>

 

Named 쿼리 환경에 따른 설정

XML이 항상 우선권을 가진다.

애플리케이션이 운영환경에 따라 다른 쿼리를 실행해야 한다면 각 환경에 맞춘 XML을 준비해 두고 XML만 변경해서 배포하면 된다.

 

벌크 연산

엔티리를 수정하려면 영속성 컨텍스트의 변경 감지 기능이나 병합을 사용하고 삭제하려면 remove메소드를 사용한다. 하지만 이방법으로 대규모의 작업을 하기에는 시간이 너무 오래 걸린다. 이렇게 여러건을 한번에 수정 삭제를 할때 벌크 연산을 사용한다.

update delete 지원

 

String qlString = "update Product p " +
 "set p.price = p.price * 1.1 " +
 "where p.stockAmount < :stockAmount";
int resultCount = em.createQuery(qlString)
 .setParameter("stockAmount", 10)
 .executeUpdate(); 

벌크 연산은 executeUpdate()메소드를 사용한다. 이 메소드는 벌크 연산으로 영향을 받은 엔티티 건수를 반환한다.

 

-주의점

벌크연산은 영속성 컨텍스트를 무시하고 db에 직접 쿼리한다. 따라서 

1.벌크연산을 먼저 실행한다.

2.벌크연산이 끝나면 영속성 컨텍스트를 초기화한다.

3.em.refresh()를 사용한다.

3가지 방법중 하나를 선택해서 해결해야한다.

 

'Jpa' 카테고리의 다른 글

값 타입  (0) 2021.01.11
프록시와 연관관계  (0) 2021.01.10
고급매핑  (0) 2021.01.10
다양한 연관관계 매핑  (0) 2021.01.10
연관관계 기초 매핑  (0) 2021.01.09