본문 바로가기
[ ORM ]/JPA

[ JPA ] Java Persistence Query Language(JPQL , 객체 지향 쿼리 언어)(2)

by 환이s 2024. 4. 11.


Intro

 

객체 지향 쿼리 언어에 대한 포스팅을 이어가 보겠습니다.

이전 포스팅에서 페이징 처리 함수까지 알아봤습니다.

 

JPQL 문법에 대해 알아보시는 분들은 아래 포스팅을 참고해 주시면 도움이 될 거 같아서 올려보겠습니다.

 

[ JPA ] Java Persistence Query Language(JPQL , 객체 지향 쿼리 언어)(1)

Intro JPA는 엔티티 객체를 중심으로 개발하기 때문에 검색 쿼리 실행하면 테이블 대상이 아닌 엔티티 객체를 대상으로 요청을 보내야 한다. 하지만 모든 데이터베이스 데이터를 객체로 변환해서

drg2524.tistory.com

 


집합과 정렬

 

집합 함수와 정렬 기능은 SQL문 작성할 때 사용할 수 있는 함수들을 별도의 처리 과정 없이 사용할 수 있다.

 

예를 들어서 집합 함수의 COUNT, MAX, MIN, AVG, SUM과 같은 통계 쿼리를 구현할 때 사용하는 함수들을

JPQL 문법으로 작성해서 실행해도 문제가 없다는 것이다.

 

집합 함수를 사용할 때는 몇 가지 참고 사항이 있는데 아래를 참고하자.

 

  • NULL 값은 무시하므로 통계에 잡히지 않는다.
    • DISTINCT가 정의되어 있어도 무시된다.
  • 값이 없는데 SUM, AVG, MAX, MIN 함수를 사용하면 NULL 값이 된다.
    • 단, COUNT는 0이 된다.
  • DISTINCT를 집합 함수 안에 사용해서 중복된 값을 제거하고 나서 집합을 구할 수 있다.
  • DISTINCT를 COUNT에서 사용할 때 임베디드 타입은 지원하지 않는다.
/* 그룹별 통계 데이터 중 평균 나이가 18살 이상인 그룹 조회 */
select t.name, COUNT(m.age), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
from Member m LEFT JOIN m.team t
GROUP BY t.name
HAVING AVG(m.age) >= 18

 

[ GROUP BY And HAVING ]

 

GROUP BY절은 통계 데이터를 가져올 때 특정 그룹끼리 묶어준다.

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

 

추가로 위 통계 쿼리에 정렬해서 출력하고 싶다면 ORDER BY 절에 상태필드 경로 및 결과 변수를 작성해 주면 된다.

ORDERBY절 :: = ORDER BY { 상태필드 경로 | 결과 변수 [ ASC | DESC ] } +

 

ASC, DESC는 오름차순으로 조회할 건지, 내림차순으로 조회할 때 추가로 같이 작성해주면 된다.


JOIN

 

하나의 데이터에 원하는 데이터가 모두 있다면 좋겠지만,

조회할 때 두 개의 테이블을 엮어야 원하는 결과가 나오는 경우가 실무에서는 정말 많다.

 

조인을 쓰면 두 개의 테이블을 엮어서 원하는 데이터를 추출할 수 있다.

그럼 JPQL 문법에서 사용할 수 있는 JOIN에 대해 알아보자.

 

[ 내부 조인(Inner Join) ]

 

내부 조인은 INNER Join이라고 불리며

두 테이블에 모두 지정한 열의 데이터가 있을 때 데이터가 출력되는 제약을 걸어준다.

 

코드로 알아보자.

String name = "nameA";
String query = "select m from Member m INNER JOIN m.team t "
             + " where t.name = :name";

List<Member> members = em.createQuery(query, Member.class)
                    .setParameter("name", name)
                    .getResultList();

 

위 코드처럼 INNER Join을 사용해서 쿼리를 작성할 수 있는데, INNER은 생략할 수 있다.

 

JPQL Join의 가장 큰 특징은 연관 필드를 사용한다는 것이다.

위 코드에서 m.team은 연관 필드이다.

 

다른 엔티티와 연관관계를 가지기 위해 사용하는 필드를 말한다.

 

JOIN 처리한 두 개의 엔티티를 조회하려면 다음과 같이 JPQL을 작성하면 된다.

 

String query = " select m, t from Member m JOIN m.team t"

List<Object[]> result = em.createQuery(query).getResultList();

for(Object[] row : result){
    Member member = (Member)row[0];
    Team team = (Team)row[1];
}

[ 외부 조인(Outer Join) ]

 

외부 조인은 두 테이블을 조인할 때, 두 테이블에 모두 지정한 열의 데이터가 있어야 한다.

select m from Member m LEFT [OUTER] JOIN m.team t

 

외부 조인도 OUTER를 생략하고 작성해도 실행된다.


[ 컬렉션 조인(Collection Join) ]

 

지금까지 예제 코드로 사용된 회원과 팀의 연관관계에 대해 생각해 보자.

<회원 -> 팀>은 다대일 조인이면서 단일 값 연관 필드(m.team)를 사용한다.

반대로 <팀 -> 회원>은 일대다 조인이면서 컬렉션 값 연관 필드(m.members)를 사용한다.

 

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

SELECT t, m FROM Team t LEFT JOIN t.members m

 

Team과 Team이 보유한 회원 목록을 컬렉션 값 연관 필드로 조인한다.


[ 세타 조인(Theta Join) ]

 

세타 조인은 전혀 관계가 없는 엔티티를 조회할 수 있는데

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

 

사용법은 WHERE절을 사용하여 작성할 수 있다.

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

[ JOIN - ON절 ]

 

ON절은 JPA 2.1 version부터 조인할 때 사용할 수 있다.

ON절은 조인 대상을 필터링하고 조인할 수 있는데,

내부 조인의 ON절은 WHERE절을 사용할 때와 결과가 같다.

 

보통 ON절은 외부 조인에서만 사용한다.

 

간단한 예시를 들어보자면

회원과 팀을 조인하면서,

팀 이름이 JPA인 팀만 조회한다고 가정했을 때 ON절을 추가해서 작성할 수 있다.

// JPQL
SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = 'JPA'

// SQL
SELECT m.*, t.* FROM Member m LEFT JOIN Team ON m.TEAM_ID=t.id and t.name='JPA'

 

 

추가로 하이버네이트 5.1부터 연관관계가 없는 엔티티 외부 조인이 가능하다.

간단한 예시로 회원의 이름과 팀의 이름이 같은 대상 외부 조인할 경우 아래와 같이 작성할 수 있다.

// JPQL
SELECT m, t FROM Member m LEFT JOIN Team t ON m.username = t.name

// SQL
SELCT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name

경로 표현식(묵시적,명시적 JOIN)

 

경로 표현식이란,

.(점)을 찍어 객체 그래프를 탐색하는 것이다.

 

경로 표현식을 이해하려면 우선 다음 용어들을 알아야 한다.

 

  • 상태 필드
    • 단순히 값을 저장하기 위한 필드
  • 연관 필드
    • 연관관계를 위한 필드, 임베디드 타입 포함
      • 단일 값 연관 필드 : @ManyToOne,@OneToOne, 대상이 Entity
      • 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 Collection
select m.username -> 상태 필드
 from Member m
 join m.team t -> 단일 값 연관 필드
 join m.orders o -> 컬렉션 값 연관 필드
where t.name = '팀A'

 

JPQL에서 경로 표현식을 사용해서 경로 탐색을 하려면

다음 3가지 경로에 따라 어떤 특징이 있는지 이해해야 한다.

 

  1. 상태 필드 경로 
    • 경로 탐색의 끝이다.
    • 더는 탐색할 수 없다.
  2. 단일 값 연관 경로 
    • 묵시적으로 내부 조인이 일어난다.
    • 단일 값 연관 경로는 계속 탐색할 수 있다.
  3. 컬렉션 값 연관 경로
    • 묵시적으로 내부 조인이 일어난다.
    • 더는 탐색할 수 없다.
    • 단, FROM절에서 Join을 통해 별칭을 얻으면 별칭으로 탐색할 수 있다.

 

예제를 통해 결로 탐색을 하나씩 알아보자.


[ 상태 필드 경로 탐색 ]

select m.username, m.age from Member m

 

상태 필드 경로 탐색은 단순히 값을 저장하기 위한 필드이다.

m.username.xxx와 같이 추가 탐색이 불가능하다. 

 


[ 단일 값 연관 경로 탐색 ]

select o.member from Order o

 

단일 값 연관 필드로 경로 탐색을 하면

SQL에서 내부 조인이 일어나는데 이것을 묵시적 조인이라 한다.

 

참고로 묵시적 조인은 모두 내부 조인이다.

SQL에서 다음과 같이 내부 조인이 발생한다.

 

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

 

참고로 임베디드 타입에 접근하는 것도 단일 값 연관 경로 탐색이지만

Entity에 이미 포함되어 있으므로 조인이 발생하지 않는다.

 


[ 컬렉션 값 연관 경로 탐색 ]

select t.members from Team t //성공
select t.members.username from Team t //실패

 

역시나 묵시적 조인이 발생한다.

 

JPQL을 다루면서 많이 실수하는 부분 중에서

하나는 컬렉션 값에서 경로 탐색을 시도하는 것이다.

 

t.members 컬렉션까지는 경로 탐색이 가능하다.

하지만 t.members.username처럼 컬렉션에서 경로 탐색을 시작하는 것은 허락하지 않는다.

 

만약 컬렉션에서 경로 탐색을 하고 싶으면

다음 코드처럼 조인을 사용해서 새로운 별칭을 획득해야 한다.

select m.username from Team t join t.members m

 

이처럼 경로 탐색을 사용하면

묵시적 조인이 발생해서 SQL에서 내부 조인이 일어날 수 있다.

 

Join은 성능 이슈를 차지하는 부분은 아주 크다.

하지만 그렇다고 해서 실무에서 묵시적 조인을 사용하지는 말자.

 

묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다는 단점이 있다.

실무에서는 예제처럼 쿼리가 간단하지 않다.

 

보통 조인해야 하는 테이블도 3~5개 정도이며, 필요하면 추가로 다른 테이블과 조인할 경우가 많다.

 

따라서 단순하고 성능에 이슈가 없으면

크게 문제가 안되지만 성능이 중요하다면 분석하기 쉽도록 묵시적 조인보다는 명시적 조인을 사용하자.


FETCH JOIN

 

Fetch Join은 JPQL에서 성능 최적화를 위해 제공하는 기능으로

연관된 엔티티나 컬렉션들을 한 번의 SQL 쿼리로 함께 조회할 수 있고,

 

연관된 엔티티에 대해 추가적인 쿼리를 실행할 필요 없이 효율적인 로드를 할 수 있는 것이다.

 

즉, 페치 조인은 성능 최적화에 주로 사용되며, N+1 문제를 해결하는 데 효과적이다.

 

예를 들어 Order와 Product Entity가 있고,

이 둘이 일대다(1 : N) 연관관계가 있다고 가정해 보자.

 

Order Entity를 조회하면서 관련된 Product(상품) Entity도 한 번의 쿼리로 함께 조회하기 위해 페치 조인을 사용할 수 있다.

SELECT o FROM Order o JOIN FETCH o.products

 

위 쿼리는 페치 조인을 사용하여 Order Entity와 관련된 Product Entity를 함께 조회한다.

이렇게 각 주문과 관련된 상품을 별도의 쿼리로 실행하는 것보다 효율적으로 데이터를 로드할 수 있다.

 

주의)

페치 조인을 사용할 때 일대다(OneToMany) 또는 다대다(ManyToMany) 관계의 엔티티가 많이 포함되면, 불 필요한 중복 데이터를 가져오는 일이 발생할 수 있다.

이 경우, DISTINCT 키워드를 사용하여 중복 데이터를 제거할 수 있다.

 

[ 일반 Join과 Fetch Join의 차이 ]

 

일반 Join과 Fetch Join의 차이는 데이터를 조회할 대 어떻게 가져오느냐에 있다.

 

먼저 일반 Join일 때를 보자.

SELECT o, p FROM Order o JOIN o.products p WHERE o.id = :orderId

 

위 일반 Join 쿼리를 보면 

Order(주문)와 Product(상품)가 있는 쇼핑몰에서 특정 주문의 상품을 알고 싶다면,

일반 Join에서는 주문과 상품 테이블을 조인하여 상품 정보를 가져온다.

 

주문에 대한 상품 정보를 받아왔지만, 해당 상품 데이터를 실제로 사용하려면 별도의 쿼리를 수행해야 한다.

 

반대로 Fetch Join을 사용하면

조회의 주체가 되는 엔티티와 그 관련 엔티티들 까지 함게 조회한다.

 

이를 객체 그래프 로드라고 하는데,

즉시 원하는 정보를 사용할 수 있도록 데이터를 로드해 온다.

 

이 방식을 통해 한 번의 쿼리로 필요한 정보를 모두 가져와서 성능을 향상시킬 수 있다.

 

정리해 보면

위 예시에서 Fetch Join을 사용하면 주문과 해당 주문에 포함된 상품 데이터를 한 번에 가져와 별도의 쿼리 없이 바로 상품 정보에 접근할 수 있다.

SELECT o FROM Order o JOIN FETCH o.products WHERE o.id = :orderId

 

그렇다면 페치 조인은 언제 사용하는 것이 좋을까?

 

개발자들 마다 각 상황에 맞게 사용하면 되지만,

크게 두 가지 상황일 때 효율적일 거 같다.

 

  • 연관 엔티티의 데이터를 자주 사용하는 경우
    • 지연 로딩의 경우 연관 엔티티를 자주 가져오는 경우에 성능 저하가 발생할 수 있다.
      • 이때, 연관 엔티티의 데이터를 함께 가져오면 지연 로딩에 따른 추가 쿼리를 줄이고 성능을 향상시킬 수 있다.
      • 즉, A만 필요할 때는 FetchType.Lazy 지연 로딩으로 A만 불러 사용하고, A와 B를 같이 부르고 싶을 경우에는 FetchType.Lazy + Fetch Join 조합을 사용하여 성능을 향상 시킬 수 있다.
  • N+1 문제가 발생하는 경우
    • N+1 문제는 연관 엔티티가 실제 사용될 때마다 별도의 쿼리가 발생하여 성능 이슈를 초래하는 것이다.
      • 이 경우, 추가적인 쿼리가 불필요하게 발생하여 성능 저하가 발생한다.
      • Fetch Join은 연관 엔티티와 함께 즉시 로딩되므로, N+1 문제를 제거할 수 있다.

 

[ Fetch Join의 한계 ]

 

Fetch Join에 선언된 엔티티에 대해서는 별칭을 사용할 수 없다.

별칭을 사용하면 열과 그 이름이 바뀔 수 있다.

 

이 경우 JPA는 연관된 엔티티 대신 별칭을 사용하여 결과를 리턴한다.

이로 인해 예기치 않은 결과를 리턴할 수 있다.

 

하지만 일관성을 해치지 않는 경우에는 별칭을 사용해도 문제가 되지 않는다.

그리고 일관성이 깨져도 조회 용도로만 사용하면 크게 문제는 되지 않는다고 한다.

 

추가로, 페치 조인 대상에는 별칭을 줄 수 없기에 SELECT, WHERE, Sub Query에 패치 조인 대상을 사용할 수 없다.

 

또한, 

둘 이상 컬렉션을 Fetch Join을 할 수 없다.

둘 이상의 컬렉션에 대해 Fetch Join을 사용하면, 결과 데이터의 크기가 곱셈 원리로 인해 급증하게 된다.

 

마지막으로

컬렉션을 Fetch Join 시 Paging 기능 사용 제한이 걸린다.

페이징 기능을 사용하여 데이터를 검색할 때, 

일반적으로 데이터베이스에서 필요한 범위의 데이터만 가져온 후 이를 화면에 표시한다.

 

그러나 Fetch Join을 사용할 경우 서로 관련된 데이터를 함께 로드하게 되며,

이 때문에 페이징 처리가 제대로 이루어지지 않을 수 있다.

 

이 문제는 OneToMany와 ManyToMany 연관관계에서 데이터가 중복되거나 데이터의 크기가 곱이 되면서 가장 뚜렷하게 나타난다.

 

하지만 OneToOne, ManyToOne 같은 단일 값 연관 필드들은 페치 조인을 해도 페이징 처리가 가능하다.


다형성 쿼리

 

 

 

Item을 상속받은 Book, Movie,. Album Entity가 있을 때, 특정 자식 타입을 조회한다고 가정해 보자.

 

이때 조회 대상을 특정 자식 타입으로 조회할 수 있는 TYPE 키워드와 

자바의 타입 캐스팅처럼 부모 타입을 특정 자식 타입으로 다루는 TREAT 키워드가 있다.

TREAT 키워드는 JPA2.1 버전부터 사용 가능하다.

 

TYPE의 사용법은 다음과 같다.

[JPQL]
select i from item i where type(i) in (Book, Movie)

 

 

[SQL]
select i.*
from   item i
where  i.dtype in ('B', 'M')

 

 

TREAT는 FROM, WHERE절에서 사용 가능하며 SELECT절에도 사용 가능하지만 하이버네이트에서 지원해 준다.

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

 

TREAT 키워드는 SQL 방언 및 테이블 전략에 맞춰서 적절한 쿼리 요청을 보내준다.


엔티티 직접 사용

 

JPQL에서 Entity를 직접 사용하면 SQL에서 해당 Entity의 기본 키 값을 사용한다.

 

엔티티의 id와 엔티티를 직접 사용하는 쿼리 요청을 보내고 결과를 확인해 보면

[JPQL]
select count(m.id) from Member m (엔티티의 id 사용)
select count(m) from Member m    (엔티티 직접 사용)
[SQL]
select count(m.id) as cnt
from   Member m

 

둘 다 동일한 SQL문으로 요청이 보내지는 걸 확인할 수 있다.

 

그렇다면 파라미터로 전달할 경우에는 어떤 결과가 나올까?

[JPQL]
select m from Member m where m.id = :memberId (엔티티의 id 사용)
select m from Member m where m = :member      (엔티티 직접 사용)

 

위 쿼리를 요청했을 때  둘 다 동일한 SQL문으로 요청이 보내지는 걸 확인할 수 있다.

[SQL]
select m.*
from   Member m
where  m.id = ?

 

외래키의 경우에도 동일하다.

[JPQL]
select m from Member m where m.team.id = :teamId  (엔티티의 id 사용)
select m from Member m where m.team = :team       (엔티티 직접 사용)

-- 둘다 아래의 SQL로 실행된다.

[SQL]
select m.*
from   Member m
where  m.team_id = ?

Named Query 

 

지금까지는 JPQL 예제를 작성하면서 String 변수에 JPQL 문법을 완성하여 수행했는데, 이를 동적 쿼리라고 한다.

 

MyBatis를 사용해 본 경험이 있다면 보통 XML에 특정 Query를 선언해 놓고 

Mapping 되는 파라미터만 변경하여 사용해 본 적이 있을 것이다.

 

이러한 것을 정적 쿼리라고 한다.

JPQL도 XML이나 애노테이션으로 정적 쿼리를 미리 작성해 놓을 수 있다.

 

NamedQuery는 애플리케이션 로딩 시점에 미리 JPQL 문법을 체크하여 파싱 한다.

오류 파악도 용이하며 미리 파싱 한 결과를 재사용하여 성능상 이점도 있다.

 

NamedQuery는 @NamedQuery 애노테이션에 작성하는 방식과 XML에 작성하는 2가지 방식이 있다.

 

[ @NamedQuery ]

 

애노테이션 방식으로 정적 쿼리를 작성하는 예제를 살펴보자.

@Entity
@NamedQuery(
	name = "Member.findMemberByName",
	query = "select m from Member m where m.name = :username"
)
public class Member extends DateMarkable{

 

@NamedQuery는 query에 수행될 JPQL을 작성해 준다.

그리고 name으로 해당 query 이름을 붙여준다.

 

위 예제에서 앞에 Member. 을 붙여준 것은 특별한 기능이 있는 것이 아니라

충돌 방지를 위해 Entity명을 붙여주었다.

 

Mybatis에서도 xml 파일에 nameSpace를 지정해 주어 쿼리 이름의 중복을 방지해 주는 것과

비슷한 기조라고 생각하면 되겠다.

 

Member member1 = new Member();

member1.setName("Name#1");
em.persist(member1);

Member member2 = new Member();

member2.setName("Name#2");
em.persist(member2);

 

위와 같이 2명의 Member를 영속화하고 아래처럼 JPQL을 작성한다.

List<Member> members = em.createNamedQuery("Member.findMemberByName", Member.class)
	.setParameter("username", "Name#1")
	.getResultList();

members.forEach(member -> {
	System.out.println("Member name: " + member.getName());
});

 

결과를 확인해 보자.

Hibernate: 
    /* Member.findMemberByName */ select
        member0_.member_id as member_i1_6_,
        member0_.insert_datetime as insert_d2_6_,
        member0_.update_datetime as update_d3_6_,
        member0_.city as city4_6_,
        member0_.street as street5_6_,
        member0_.zipcode as zipcode6_6_,
        member0_.name as name7_6_ 
    from
        member member0_ 
    where
        member0_.name=?
Member name: Name#1

 

실행된 SQL을 보면 JPQL이 아니라 @NamedQuery에 name 속성이 주석으로 나오는 걸 알 수 있다.

 

NamedQuery를 사용할 때에는 Entity Manager의 createNamedQuery 메서드를 사용한다.

만약 NamedQuery를 여러 개 사용하려면 @NamedQueries 애노테이션을 사용해 주면 된다.

@Entity
@NamedQueries({
	@NamedQuery(
		name = "Member.findMemberByName",
		query = "select m from Member m where m.name = :username"),
	@NamedQuery(
			name = "Member.findMemberById",
			query = "select m from Member m where m.id = :memberId")
})
public class Member extends DateMarkable{

 

 

[ NamedQuery XML 정의 ]

 

실제 프로젝트에서도 위의 예제처럼 간단한 JPQL만 작성하면 좋겠지만 현실은 그렇지 않다.

멀티 라인을 지원하지 않는 언어라면 쿼리가 조금만 길어져도 파악하기 상당히 힘들다.

 

이럴 경우에는 별도의 파일에 쿼리를 관리하는 게 좋다.

XML에 NamedQuery를 작성할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/" version="2.1">

	<named-query name="Member.findByUsername">
		<query><![CDATA[
			select m
			from Member m
			where m.name = :username
			]]>
		</query>
	</named-query>
	
</entity-mappings>

...........

 <persistence-unit name="jpaMember">
		<mapping-file>META-INF/member.xml</mapping-file>

 

member.xml에 named-query를 작성한 다음 persistence.xml의  mapping-file에 해당 파일 경로를 mapping 시켜준다.

 

그 후 createNamedQuery를 이용하여 수행해 주면 @NamedQuery 애노테이션과 동일하게 동작한다.

 

만약 @NamedQuery와 XML에서 같은 이름을 부여했다면 XML 우선권을 갖는다.


벌크 연산

 

모든 Member의 나이를 30살로 바꿔야 된다고 가정해 보자.

JPA 변경 감지 기능으로 해결하려면 너무 많은 SQL이 실행된다.

 

그래서 JPA는 한 번의 쿼리로 여러 테이블의 로우를 변경할 수 있는 기능을 제공한다.

 

[ executeUpdate() ]

  • update, delete 지원
  • 하이버네이트에서는 insert into... select 지원
int resultCount = em.createQuery("update Member p set m.age = 30", Member.class)
                  .executeUpdate(); //업데이트 된 엔티티의 수를 반환한다.

 

 

벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리 한다.

그렇기 때문에 영속성 컨텍스트와 실제 데이터베이스 사이의 데이터 정합성 문제가 발생할 수 있다.

 

벌크 연산을 사용할 때에는 주의 사항으로

영속성 컨텍스트에 엔티티가 들어가기 전에 벌크 연산을 먼저 수행하거나 벌크 연산 수행 후 영속성 컨텍스트를 초기화해줘야 한다.

int resultCount = em.createQuery("update Member p set m.age = 30", Member.class)
                  .executeUpdate();
//em.clear(); 영속성 컨텍스트를 초기화 하지 않으면
  
Member member1 = em.find(Member.class, 1L);
member.getAge(); // 0 출력

 


마치며

 

오늘 포스팅으로 객체지향 쿼리 언어 파트 부분을 마무리하겠습니다.

현재에는 실무에서 프로젝트를 진행하면서 토이 프로젝트 설계도 하고 있다 보니

포스팅이 많이 느린 거 같습니다.

 

다음 포스팅은 토이 프로젝트 하면서 기능 구현 및 결함 처리 등으로 찾아뵙겠습니다.

위 포스팅 글은 김영한님의 자바 ORM 표준 JPA 프로그래밍-기본편을 참고했습니다.

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 | 김영한 - 인프런

김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도

www.inflearn.com

 

 

728x90