새소식

back/spring

Orm 표준 jpa 프로그래밍 (6) - 객체 지향 쿼리 언어

  • -

 

0.

 

- JPA는 다양한 쿼리 방법을 지원 (JPQL / JPA Criteria / QueryDSL / 네이티브SQL / JDBC API 직접 사용 등 ...)

 

조회 방법1

- EntityManager.find()

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

 

- JPA를 사용하면 엔티티 객체를 중심으로 개발

- 단, 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색

- 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능

- 어플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL 필요함

 

 

<JPQL>

 

- JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공

- SQL과 유사한 문법(select, from, where, group by, having, join 지원)

- JPQL은 엔티티 객체를 대상으로 쿼리

- SQL은 DB테이블을 대상으로 쿼리

 

//검색
String jpql = "select m From Member m where m.name like ‘%hello%'";

List<Member> result = em.createQuery(jpql, Member.class).getResultList();

// JPQL 쿼리를 통해 얻은 결과 리스트를 저장
// 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리

 

- SQL을 추상화해서 특정 DB SQL에 의존X

 

 

//검색
String jpql = "select m from Member m where m.age > 18";
List<Member> result = em.createQuery(jpql, Member.class)
        .getResultList();

 

실행된 SQL 
	select
          m.id as id,
          m.age as age,
          m.USERNAME as USERNAME,
          m.TEAM_ID as TEAM_ID
	from
		Member m
    where
        m.age>18

 

 

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

- 문자가 아닌 자바코드로 JPQL을 작성할 수 있음

- JPQL 빌더 역할

 - 컴파일 시점에 문법 오류 찾을 수 있음

- 동적 쿼리 작성이 편리함

- 단순하고 쉬움(실무 사용 권장) 

 

 

<네이티브 SQL>

- JPA가 제공하는 SQL을 직접 사용하는 기능

- JPQL로 해결할 수 없는 특정 DB에 의존적인 기능(오라클 CONNECT BY, 특정 DB만 사용하는 SQL 힌트)

String sql = “SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = ‘kim’";
List<Member> resultList =
            em.createNativeQuery(sql, Member.class).getResultList();

 

<JDBC 직접 사용 SpringJdbcTemplate 등..>

- jpa를 사용하면서 jdbc 커넥션을 직접 사용하거나, 스프링 jdbcTemplate, 마이바티스등을 함께 사용 가능

(단, 영속성 컨텍스트를 적절한 시점에 강제로 플러시 필요)

ex) jpa를 우회해서 sql을 실행하기 직전에 영속성 컨텍스트 수동 플러시

 

 

 

1. JPQL(Java Persistence Query Language)

- 엔티티 객체를 대상으로 쿼리하는 객체 지향 SQL

- 결국 SQL로 변환됨

select m from Member as m where m.age > 18

- 엔티티와 속성은 대소문자 구분O (Member, age)

- JPQL 키워드는 대소문자 구분X (SELECT, FROM, where)

- 엔티티 이름 사용, 테이블 이름이 아님(Member)

- 별칭 필수(위 예제는 'm', as는 생략가능)

 

집합(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");
// 이름과 나이는 각각 String과 int

 

// 결과 조회 API
query.getResultList()
// 결과가 하나 이상일 때, 리스트 반환
// 결과가 없으면 빈 리스트 반환

query.getSingleResult(): 
// 결과가 정확히 하나, 단일 객체 반환
// 결과가 없으면: javax.persistence.NoResultException
// 둘 이상이면: javax.persistence.NonUniqueResultException
 
 
// 파라미터 바인딩 - 이름 기준 / 위치 기준
SELECT m FROM Member m where m.username=:username 
query.setParameter("username", usernameParam);

SELECT m FROM Member m where m.username=?1
query.setParameter(1, usernameParam);


// 프로젝션
// SELECT 절에 조회할 대상을 지정하는 것
// 프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)
SELECT m FROM Member m // 엔티티 프로젝션
SELECT m.team FROM Member m // 엔티티 프로젝션
SELECT m.address FROM Member m // 임베디드 타입 프로젝션
SELECT m.username, m.age FROM Member m // 스칼라 타입 프로젝션
// DISTINCT로 중복 제거

 

프로젝션 이용한 여러 값 조회

ex) SELECT m.username, m.age FROM Member m

1. Query 타입으로 조회

2. Object[] 타입으로 조회

3. new 명령어로 조회 

>>

// 단순값을 DTO로 바로 조회
SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
// 패키지명을 포함한 전체클래스명 입력
// 순서와 타입이 일치하는 생성자 필요

- 두가지 이상 조회시에는 new 패키지명.클래스명(조회내역) 이용

 

 

페이징 API

- JPA는 페이지을 다음 두 API로 추상화

- setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)

- 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) // 위치 10부터 20개 
      .getResultList();

 

 

조인

//내부 조인
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 이용)
// 예) 회원과 팀을 조인하면서, 팀 이름이 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'


// 연관관계 없는 엔티티 외부 조인
// 예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
//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

 

 

서브 쿼리

// 나이가 평균보다 많은 회원
select m from Member m
	where m.age > (select avg(m2.age) from Member m2)
 
//한 건이라도 주문한 고객
select m from Member m
	where (select count(o) from Order o where m = o.member) > 0

 

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

- {ALL | ANY | SOME} (subquery) : ALL은 모두 만족하면 참, ANY / SOME 은 같은 의미, 조건을 하나라도 만족하면 참

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

// 팀A 소속인 회원
select m from Member m
where exists (select t from m.team t where t.name = ‘팀A')

// 전체 상품 각각의 재고보다 주문량이 많은 주문들
select o from Order o
where o.orderAmount > ALL (select p.stockAmount from Product p)

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

- JPA는 where / having 절에서만 서브 쿼리 사용 가능

- select 절도 hibernate에서 지원함

- 단, from절의 서브 쿼리는 현재 JPQL에서 불가능 >> 조인으로 풀어낼 수 있으면 이 방법을 통해야함 >> hibernate6 부터 지원

 

 

JPQL의 타입 표현

1. 문자

2. 숫자: 10L / 10D / 10F

3. Boolean

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

5. 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)

 

 

- 조건식

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

// 단순 case 식
select
	case t.name
		when '팀A' then '인센티브110%'
        when '팀B' then '인센티브120%'
        else '인센티브105%'
	end
from Team t

 

- COALESCE: 하나씩 조회해서 null이 아니면 반환

- NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환

// 사용자 이름이 없으면 이름 없는 회원을 반환
select coalesce(m.username,'이름 없는 회원') from Member m
// 사용자 이름이 ‘관리자’면 null을 반환하고 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m

 

- 사용자 정의 함수 호출: hibernate는 사용전 dialect에 추가해야 함(사용하는 DB dialect를 상속받고, 사용자 정의 함수를 등록함)

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

 

 

2. 경로 표현식

- . 을 찍어 객체 그래프를 탐색

select m.username // 상태 필드
	from Member m
		join m.team t // 단일 값 연관 필드
		join m.orders o // 컬렉션 값 연관 필드
where t.name = '팀A'

1. 상태 필드: 단순히 값을 저장하기 위한 필드 (m.username)

- 경로 탐색의 끝, 탐색X

 

2. 연관 필드: 연관관계를 위한 필드 

- 단일 값 연관 필드: @ManyToOne / @OneToOne, 대상이 엔티티(m.team), 묵시적 내부 조인 발생, 탐색O

- 컬렉션 값 연관 필드: @OneToMany / @ManyToMany, 대상이 컬렉션(m.orders), 묵시적 내부 조인 발생, 탐색X >> from 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색 가능 

 

- 상태 필드 경로 탐색

1. JPQL: select m.username, m.age from Member m

2. SQL: select m.username, m.age from Member m

 

- 단일 값 연관 경로 탐색

1. JPQL: select o.member from Order o

2. SQL: select m.* from Orders o inner join Member m on o.member_id = m.id

 

- 명시적 조인과 묵시적 조인 

1. 명시적 조인: join 키워드 직접 사용 (select m from Member m join m.team t)

2. 묵시적 조인: 경로 표현식에 의해 묵시적으로 SQL 조인 발생( 내부 조인만 가능 ) (select m.team from Member m) 

 

- [JPQL] select t from Member m join m.team t 에서 ' join m.team t '는 m과 team을 조인하고 Ailas으로 t를 사용한다는 뜻

// 경로 표현식 예제
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 (join) 절에 영향을 줌

 

- 묵시적 조인보다는 명시적 조인 권장

- 조인은 sql 튜닝에 중요

- 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어려움

 

 

3. fetch join

- sql의 조인의 종류가 아님

- JPQL에서 성능 최적화를 위해 제공하는 기능

- 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능

- join fetch 명령어 사용

 

엔티티 패치 조인 

// 회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
// SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT
// 함께 '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

 

 

 

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 = 팀A
// username = 회원3, teamname = 팀B

 

컬렉션 패치 조인

-

// 일대다 관계
// [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'

 

 

- DB 입장에서는 일대다 조인을 하면 데이터가 그에 맞게 늘어남 (다대일은 변화X)

- 위와 같은 중복을 해결하기 위한 방안: DISTINCT

 

String jpql = "select t from Team t join fetch t.members where t.name = '팀A'"
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();

for(Team team : teams) {
	System.out.println("teamname = " + team.getName() + ", team = " + team);
    for (Member member : team.getMembers()) {
	//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
		System.out.println(“-> username = " + member.getUsername()+ ", member = " + member);
    }
}

// 출력(팀A는 회원이 2명이어서 두번 출력됨)
// teamname = 팀A, team = Team@0x100
// -> username = 회원1, member = Member@0x200 
// -> username = 회원2, member = Member@0x300 
// teamname = 팀A, team = Team@0x100
// -> username = 회원1, member = Member@0x200 
// -> username = 회원2, member = Member@0x300

 

페치 조인과 DISTINCT

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

- JPQL은 ( 1. SQL에 DISTINCT 를 추가 2. 어플리케이션에서 엔티티 중복 제거 )의 2가지 기능을 제공함

>> SQL에 DISTINCT를 추가해서 DB에 쿼리를 보내서 그 결과가 어플리케이션에 올라왔을 때 똑같은 엔티티가 있으면 제거

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

// 1. SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과 에서 중복제거 실패
// ID나 NAME 등 컬럼값이 하나라도 다르면 중복으로 취급 안됨

// 2. DISTINCT가 추가로 애플리케이션에서 중복 제거시도
// >> '같은 식별자'를 가진 Team 엔티티 제거 (예제는 팀A )

>> 사용자가 DB SQL의 결과 수 만큼 그대로 가져올지 DISTINCT해서 엔티티 입장에서 중복인것을 걸러서 가져올지 선택 가능

 

-  hibernate6 부터는 DISTINCT 명령어를 사용하지 않아도 애플리케이션에서 중복 제거가 자동으로 적용됨

 

 

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

A.

- 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않음

(조인만 함, 데이터는 주 엔티티의 것만 조회됨)

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

- JPQL은 결과를 반환할 때 연관관계를 고려하지 않고, select절에 지정한 엔티티만 조회

위 예제에서는 팀 엔티티만 조회하고 회원 엔티티는 조회하지 않음 )

 

B.

- 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)

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

// 페치 조인은 연관된 엔티티를 함께 조회
// [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'

 

페치 조인의 특징 / 한계

 

1. 페치 조인 대상에는 as 안됨(hibernate 는 가능하지만 , 가급적 사용X)

>> 연관된 엔티티의 것을 모두 조회 (객체 그래프를 유지하는데 효과적!)

ex) 팀을 조회하는데 연관된 멤버 엔티티의 멤버 수가 총 5명일 때 3명만 따로 조작한다는 것은 데이터와 관련하여 위험한 행동!!

jpa에서 .을 통해 객체 그래프를 탐색 할 때는 데이터를 모두 조회해야 함!(걸러야 할 때는 select에서 처음부터 따로 조회)

 

2.  둘 이상의 컬렉션은 페치 조인 불가

(일대다 조인도 데이터가 뻥튀기 되는 상황 > 일대다대다는 n*n 이라서 데이터가 수없이 뻥튀기 될 수 있음 )

 

3. 컬렉션을 페치 조인하면 페이징API(setFirstResult, setMaxResults)를 사용할 수 없음

(단, 일대일 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능)

>> 컬렉션을 조인하게 되면 데이터 뻥튀기가 일어날 수 있기에 페이징 API가 원하는 대로 동작하지 않게 되는데,

일대일 다대일은 데이터 뻥튀기와 관련된 문제가 없기에 페이징 API 사용 가능

 

- 페이징은 DB 중심적 (DB에서 row수를 줄이는게 목적이기 때문)

- hibernate는 경고 로그를 남기고 메모리에서 페이징함(매우 위험한 방법)

> 페이지 쿼리가 따로 없음(모든 것을 다 조회(객체 그래프 탐색 관련)하는데 이것을 메모리내에서 하는 것은 매우 위험함 )

 

String query = "select t From Team t join fetch t.members m";
// 1. 일대다 관계를 아래와 같이 다대일로 변경하면 페이징 관련 문제를 해결할 수 있음
String query = "select m From Member m join fetch m.team t";
// 2. 또는 페치 조인 명령어를 제거
String query = "select t From Team t";


// 팀A에 회원2명 / 팀B에 회원 1명 있는 상황
for(Team team : result){
	syso("team = " + team.getName() + "|members=" + team.getMembers());
    for(Member member : team.getMembers()){
    	syso("->member = " + member);
    }
}
// 쿼리: 팀 리스트 조회 / 팀A 조회(LAZY로딩) / 팀B 조회(LAZY로딩) > 총 3번 쿼리
// 성능면에서 좋지 않음(쿼리 갯수가 1+N(LAZY로딩 수) 방식)
// Team 엔티티에서 List<Member> members에 @BatchSize(size = n) 적용하면 n만큼 쿼리를 넘김
// @BatchSize을 이용하면 1+N 문제를 해결할 수 있음, 페치 조인으로 해결이 가능한데 컬렉션일 땐 안됨

 

 

 

연관된 엔티티들을 SQL한번으로 조회 > 성능 최적화

- 엔티티에 직접 적용하는 글로벌 로딩 전략(@OneToMany(fetch = FetchType.LAZY))보다 우선

// (예제 파일의 META-INF의 persistence.xml 에서 추가, value값은 1000 이하의 적절한 값)
<property name="hibernate.default.batch_fetch_size" value="100"/>

- 실무에서 글로벌 로딩 전략은 모두 지연 로딩 

- 최적화가 필요한 곳은 페치 조인 적용 (jpa 대부분의 성능 문제는 페치 조인으로 해결됨)

- 객체 그래프를 유지할 때 사용하면 효과적

 

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

>> 3가지 방법이 있음

1. 페치 조인을 통해 엔티티 조회

2. 페치 조인 과정을 거치고 그것을 어플리케이션에서 DTO로 변환해서 반환

3. 처음부터(JPQL작성때) new 오퍼레이션으로 DTO를 스위칭해서 가져오기

 

 

4. 다형성 쿼리

- ex: 슈퍼타입 테이블 Item과 서브타입 테이블 Album, Movie, Book 이 존재

// 조회 대상을 특정 자식으로 한정
// 예) Item 중에 Book, Movie를 조회
// [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(hibernate 지원)

// 예) 부모인 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.author = ‘kim’

 

 

5. 엔티티 직접 사용 / Named 쿼리 / 벌크 연산

 

엔티티 직접 사용

// JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용
// [JPQL]
select count(m.id) from Member m //엔티티의 아이디를 사용
select count(m) from Member m //엔티티를 직접 사용

// [SQL](JPQL 둘다 같은 다음 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();
    
// 식별자를 직접 전달
String jpql = “select m from Member m where m.id = :memberId”;
List resultList = em.createQuery(jpql)
	.setParameter("memberId", memberId)
    .getResultList();
    
// 실행된 SQL
select m.* from Member m where m.id=?
    
    
// 엔티티 직접 사용 - 외래 키 값
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();
    
String qlString = “select m from Member m where m.team.id = :teamId”;
List resultList = em.createQuery(qlString)
	.setParameter("teamId", teamId)
    .getResultList();
    
// 실행된 SQL
select m.* from Member m where m.team_id=?

 

Named 쿼리

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

- 정적 쿼리

- 어노테이션 / XML 에 정의함

- 어플리케이션 로딩 시점에 초기화 후 재사용, 어플리케이션 로딩 시점에 쿼리를 검증

- 어노테이션과 XML 중에는 XML이 항상 우선권을 가짐

- 어플리케이션 운영 환경에 따라 다른 XML 배포 가능

// 1. 어노테이션
@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();

 

 

// 2. XML
[META-INF/persistence.xml]
<persistence-unit name="jpabook" >
	<mapping-file>META-INF/ormMember.xml</mapping-file>
    
[META-INF/ormMember.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>

 

 

벌크 연산

 

ex: 재고가 5개 미만인 모든 상품의 가격을 12% 상승하려면?

>>

A1. JPA 변경 감지 기능

- 너무 많은 SQL 문 수행 (변경된 데이터가 10000건이라면 10000번의 UPDATE SQL 실행)

(1. 재고가 5개 미만인  상품을 리스트로 조회하고 2. 상품 엔티티의 가격을 12% 증가시키고 3. 트랜잭션 커밋 시점에 변경감지가 동작)

 

-  벌크 연산은 한번의 쿼리로 여러 테이블의 로우를 변경 시킴

- executeUpdate()의 결과는 영향받은 엔티티 수 반환

- UPDATE, DELETE 지원

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

 - 벌크 연산은 영속성 컨텍스트를 무시하고 DB에 직접 쿼리

(벌크 연산을 먼저 실행하고 그 후 영속성 컨텍스트 초기화)

 

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.