본문 바로가기

SpringFramework/JPA

JPA - 객체지향 쿼리 언어, JPQL

JPQL(JavaPersistence Query Language)은 가장 중요한 객체지향 쿼리 언어이다. Criteria나 QueryDSL은 이 JPQL을 편리하게 사용하도록 도와주는 기술이며, JPQL에서 시작한다.

JPQL의 특징

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

    - SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않도록 함

    - SQL이 데이터베이스 테이블이 대상인 데이터 중심의 쿼리라면, JPQL은 엔티티 객체를 대상으로 하는 객체지향 쿼리이다.

    - JPQL을 사용하면 JPA가 이를 분석하여 적절한 SQL을 만들어서 DB를 조회한다. 그리고 조회 결과를 엔티티 객체를 생성하여 반환한다.

    - 엔티티 직접 조회, 묵시적 조인, 다형성 등을 지원하여 SQL보다 코드가 간결하다.

    - JPQL로 엔티티를 조회하면 영속성 컨텍스트에서 관리한다. 하지만 엔티티가 아닌경우(예를들어 임베디드 타입)에는 값을 변경해도 영속성 컨텍스트가 관리하지 않으므로 변경 감지에 의한 수정이 발생하지 않는다.

 

- JPQL로 조회한 엔티티와 영속성 컨텍스트

: JPQL로 데이터베이스에서 조회한 엔티티가 영속성 컨텍스트에 이미 있으면, JPQL로 데이터베이스에서 조회한 결과를 버리고 대신에 영속성 컨텍스트에 있던 엔티티를 반환한다. (식별자 값으로 비교)

    - 순서

    1] JPQL을 사용해서 조회를 요청한다

    2] JPQL은 SQL로 변환되어 데이터베이스를 조회

    3] 조회한 결과를 영속성 컨텍스트와 비교

    4-1] 식별자값 기준으로 값이 영속성 컨텍스트에 이미 있다면, DB조회값은 버리고 영속성 컨텍스트 값을 반환

    4-2] 식별자값 기준으로 값이 영속성 컨텍스트에 없다면, DB조회 값을 영속성 컨텍스트에 추가하여 반환

- find() vs JPQL

    find()는 영속성 컨텍스트에서 먼저 찾고, 값이 없으면 DB조회

    JPQL은 항상 DB를 먼저 조회하여 영속성 컨텍스트에 값이 없으면 영속상태로 넣고 기존 값이 있으면 버린다.

위의 차이점이 있지만 둘은 모두 값이 있을때 영속성 컨텍스트의 값을 반환한다.

JPQL 사용 기본 예제

@Entity(name = "Member")
public class Member {
    private int id;
    @Column(name = "name")
    private String userName;
    
    private Team team;
    
    private String name;
}

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPQL_Start");
    EntityManager em = emf.createEntityManager();

    String jpql = "selct m from Member as m where m.username = 'xggames'"; // JPQL 문법
    List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();
}

// 실제 실행된 SQL
/*
 * select
 *     member.id as id,
 *     member.age as age,
 *     member.team_id as team,
 *     member.name as name
 * from
 *     Member member
 * where
 *     member.name='xggames'
 */

 

 

select_문 :: =
    select_절
    from_절
    [where_절]
    [groupby_절]
    [having_절]
    [orderby_절]    
// ex
SELECT m FROM Member AS m WHERE m.username = 'xggames'

update_문 :: update_절 [where_절]
delete_문 :: delete_절 [where_절]

    - 대소문자 구분 [엔티티, 속성은 구분] / [예약어(키워드)는 구분안함]

    - 엔티티 이름 - 예제의 Member는 테이블명이 아닌 엔티티명이다.

    - 별칭은 필수로 작성 - 별칭을 기입하지 않으면 문법오류 발생 (속성앞에 alias가 들어가야함) (단, AS 키워드는 생략가능)

TypeQuery, Query

: 작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 한다. 쿼리 객체는 반환할 타입을 명확하게 지정할 수 있으면 TypeQuery, 반환 타입을 명확하게 지정할 수 없으면 Query를 사용한다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPQL_Query");
EntityManager em = emf.createEntityManager();

// Member에 username(String), age(Integer)가 있다고 가정

// TypedQuery 사용 - Member 엔티티로 정확하게 받는다.
TypedQuery<Member> query1 = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList1 = query1.getResultList();

for (Member member : resultList1) {
    // member // Member객체
}

// Query 사용 - 반환할 타입이 명확하지 않을때.. (String, Integer 등 여러가지)
Query query2 = em.createQuery("SELECT m.username, m.age FROM Member m");
List<Member> resultList2 = query2.getResultList();

for (Object data : resultList2) {
    Object[] result = (Object[]) data; // SELECT 필드 결과가 둘 이상이면 Object[] 배열 반환
    // result[0] // Object
    // result[1] // Object
}

    - query.getResultList(): 결과를 List로 반환. 결과값이 없드면 빈 컬렉션 리턴

    - query.getSingleResult(): 결과가 정확히 하나일 때 사용. 결과가 없으면(NoResultException), 결과가 2개이상이면 (NonUniqueResultException) 예외가 발생

파라미터 바인딩

// .. EntityManager 생략

String nameParam = "xggames";

// binding
String jpql = "SELECT m FROM Member m WHERE m.username = :username";
TypedQuery<Member> query = em.createQuery(jpql, Member.class);

// parameter set
query.setParameter("username", nameParam);
List<Member> resultList = query.getResultList();

// 연속작성 가능
/*
List<Member> members = em.createQuery(jpql, Member.class)
    .setParameter("username", nameParam)
    .getResultList();
 */
 
 // 위치기준 파라미터
String jpql2 = "SELECT m FROM Member m WHERE m.username = ?1";
List<Member> members2 = em.createQuery(jpql2, Member.class)
    .setParameter(1, nameParam)
    .getResultList();

- [:이름] 기준 파라미터를 정의하고 query.setParameter("이름", param);으로 파라미터를 바인딩한다.

- 위치기준 파라미터보다는 이름 기준 파라미터 바인딩 방식이 더 명확하며, 많이 사용한다.

- 파라미터 바인딩 방식은 선택이 아닌 필수다. 문자열을 더하여 만드는 방법은 인젝션 공격에 노출될 수 있다. 그리고 JPA는 JPQL을 SQL로 파싱한 결과를 재사용할 수 있으며, 전체 성능 향상에도 도움을 줄 수 있다.

프로젝션

: 앞서 TypedQuery, Query에서 예제코드로 확인했던 SELECT 절에 조회할 대상을 지정하는 것을 말한다. [SELECT 프로젝션대상]

 

- 엔티티 프로젝션

: 원하는 객체를 바로 조회한다. 이러게 조회된 엔티티는 영속성 컨텍스트에서 관리된다.

    ex) SELECT m FROM Member m

 

- 임베디드 타입 프로젝션

: 임베디드는 조회의 시작점이 될 수 없으며, 엔티티를 통해 임베디드 타입을 조회할 수 있다.

// EntityManager 생성...생략

// @Embedded Order
// @Embeddable Address

String jpql = "SELECT o.address FROM Order o";
List<Address> addresses = em.createQuery(jpql, Address.class).getResultList();

/* SQL 실행결과
    select
        order.city,
        order.street,
        order.zipcode
    from
        Orders order
*/

    - 임베디드 타입은 값타입이므로, 영속성 컨텍스트에서 관리되지 않는다.

 

- 스칼라 타입 프로젝션

: 숫자, 문자, 날짜 등.. 기본 데이터 타입

Double totalAvg = em.createQuery("SELECT AVG(o.amount) FROM Order o", Double.class).getSingleResult();

 

- 여러값 조회

: 앞서 봤던 Query타입 형태로 반환할 타입이 명확하지않을때(=반환 타입이 여러가지일때)

 

- NEW명령어

// EntityManager 생성..생략

// Object[]로 객체변환작업 대신.. new 키워드 사용
String jpql = "SELECT new com.ys.test.lab.dto.UserDTO(m.username, m.age) FROM Member m";
TypedQuery<UserDTO> query = em.createQuery(jpql, UserDTO.class);

List<UserDTO> resultList = query.getResultList();

- 경로 표현식(Path Expression)

: [.](점)을 찍어 객체 그래프를 탐색하는 것. 예를들어 m.username, m.team, t.name 등과 같은 표현들이다.

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

    => 경로 탐색의 끝이다. 더는 탐색할 수 없다.

    - 단일 값 연관 필드: 연관관계를 위한 필드, 임베디드 타입 포함하며 @ManyToOne, @OneToOne 대상 엔티티

    => 묵시적으로 내부 조인이 일어난다. 단일 값 연관 경로는 계속 탐색할 수 있다.

    - 컬렉션 값 연관 필드: 연관관계를 위한 필드, 임베디드 타입 포함하며 @OneToMany, @ManyToMany 대상 컬렉션

    => 묵시적으로 내부 조인이 일어난다. 더는 탐색할 수 없다. (단, FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색이 가능)

 

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

    - 항상 내부 조인이다.

    - 컬렉션은 경로 탐색의 끝이므로, 컬렉션에서 경로 탐색을 하려면 명시적으로 조인해서 별칭을 얻어야 한다. (아래 예제 4번참고)

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

    - 묵시적 조인은 파악이 어렵기 때문에, 가능하다면 명시적 조인을 사용하자.

// -- 1] 상태 필드 경로 탐색 --
String sql1 = "SELECT m.username, m.age FROM Member m";

// -- 2] 단일 값 연관 경로 탐색 --
// 묵시적 조인 발생 (내부 조인만)
String sql2 = "SELECT o.member from Order o";
/* SQL 실행
    SELECT
        m.*
    FROM
        Orders o
        INNER JOIN Member m ON o.member_id = m.id
*/

// -- 3] 조금 더 복잡한 JPQL 예제 --
String sql3 = "SELECT o.member.team FROM Order o " +
    "WHERE o.product.name = '상품명A' AND o.address.city = '서울'";
/* SQL 실행
    SELECT
        t.*
    FROM
        Order o
        INNER JOIN Member m ON o.member_id = m.id
        INNER JOIN Team t ON m.team_id = t.id
        INNER JOIN Product p ON o.product_id = p.id
    WHERE
        p.name = '상품명A' AND o.city = '서울'
*/
// o.address는 임베디드타입

// -- 4] 컬렉션 값 연관 경로 탐색방법 --
// String sql4 = "select t.members.username from Team t"; // 실패
String sql4 = "select m.username from Team t join t.members m";

// -- 5] 컬렉션 값 size 기능 사용
String sql5 = "select t.members.size from Team t"; // SQL변환시 COUNT 함수 사용과 같은 효과

페이징 API

메소드 설명
setFirstResult(int startPosition) 조회 시작 위치(0부터 시작)
setMaxResults(int maxResult) 조회할 데이터 수
// EntityManager 생성..생략

String jpql = "SELECT m FROM Member m";
TypedQuery<Member> query = em.createQuery(jpql, Member.class);

List<UserDTO> resultList = query.setFirstResult(0)
    .setMaxResults(20)
    .getResultList();

=> 페이징 처리방법은 데이터베이스마다 다른 SQL 방언(Dialect)에 상관없이 같은 코드로 개발할 수 있다.

JPQL의 여러 함수들

JPQL의 함수들은 대부분 SQL과 비슷하다. 사용가능한 함수들을 속성별로 구분하여 정리하였다.

 

- 집합, 정렬함수

    - NULL값은 무시된다.

    - 값이 없는데 집합함수가 사용되면 NULL값으로 리턴한다. (COUNT는 0으로 리턴)

    - SELECT COUNT(DISTINCT m.age) FROM Member m => 중복값 제거후 집합을 구할 수 있다. (이때 임베디드 타입은 지원하지 않는다.)

함수 설명 리턴타입
COUNT(m) 결과 수를 구한다. Long
MAX(m.age), MIN(m.age) 최대, 최소 값을 구한다. 문자 숫자 날짜 등에 사용  
AVG 평균 값을 구한다. 숫자타입만 사용가능 Double
SUM 합을 구한다. 숫자타입만 사용가능 정수합: Long
소수합: Double,
BigInteger, BigDecimal
     
GROUP BY 통계 데이터 구할 때 특정 그룹끼리 묶는다.
=> GROUP BY 속성
 
HAVING GROUP BY로 그룹화한 통계 데이터를 기준으로 필터링
=> HAVING AVG(m.age) >= 10 : 평균나이가 10살이상
 
ORDER BY 결과 정렬
- ASC: 오름차순 (default)
- DESC: 내림차순
 

 

- 서브 쿼리

    - WHERE, HAVING절에서만 사용가능하고 SELECT, FROM절에서는 사용할 수 없다.

키워드 설명 문법 & 예제
[NOT] EXISTS 서브쿼리에 결과가 존재하면 참(NOT은 반대) WHERE EXISTS (SELECT t FROM m.team t WHERE t.name = 'xggames')
1] ALL
2] ANY or SOME
1] 조건을 모두 만족하면 참
2] 조건을 하나라도 만족하면 참
+ 비교 연산자와 같이 사용한다
1] WHERE o.amount > ALL (SELECT p.productAmount FROM Product p)
2] WHERE m.team = ANY (SELECT t FROM Team t)
IN 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참 SELECT t FROM Team t
WHERE t IN (SELECT t2 FROM Team t2 JOIN t2.member m2 WHERE m2.age >= 20)

 

- 조건식

키워드 설명 문법
AND 둘다 만족하면 참 A AND B
OR 둘 중 하나만 만족해도 참 A OR B
NOT 조건식의 결과 반대 NOT IN ("String")
BETWEEN X는 A와 B 사이의 값이면 참 (A와 B포함) X [NOT] BETWEEN A AND B
IN X와 같은 값이 expression에 하나라도 있으면 참 X [NOT] IN (expression)
LIKE 문자표현식과 패턴값을 비교 LIKE '패턴값' [ESCAPE문자]
IS [NOT] NULL NULL인지 비교
(NULL은 = 로 비교할 수 없다)
 

 

- 컬렉션 식

    - 컬렉션에만 사용하는 특별한 기능이다. 컬렉션은 컬렉션 식 이외에 다른 식은 사용할 수 없다.

키워드 설명 예제
{컬렉션} IS [NOT] EMPTY 컬렉션에 값이 비었으면 참 WHERE m.orders IS NOT EMPTY
{엔티티 or 값} [NOT] MEMBER [OF] {컬렉션} 엔티티나 값이 컬렉션에 포함되어 있으면 참 WHERE 'xggames' MEMBER OF t.members

 

- 스칼라식

    - 숫자, 문자, 날짜, case 등 가장 기본적인 타입

    - 사칙연산 등을 포함

키워드 설명 예제
CONCAT(문자1, 문자2) 문자를 합친다. CONCAT(A, B) // AB
SUBSTRING(문자, 위치, [길이]) 위치부터 시작해 길이만큼 문자를 구한다.
([길이]가 없으면 나머지 전체 길이)
SUBSTRING('ABCDE', 2, 3) // BCD
TRIM 공백제거 TRIM('  ABC ') // ABC
LOWER(문자) 소문자로 변경 LOWER('ABC') // abc
UPPER(문자) 대문자로 변경 UPPER('Abc') // ABC
LENGTH(문자) 문자 길이 LENGTH('ABCD') // 4
LOCATE(찾을문자, 원본문자, [검색시작위치]) 검색위치부터 문자를 검색한다. 1부터 시작
(못찾으면 0리턴)
LOCATE('E', 'ABCDEFG') // 5
     
ABS(수학식) 절대값을 구함 ABS(-10) // 10
SQRT(수학식) 제곱근을 구함 SQRT(4) // 2.0
MOD(수학식, 나눌 수) 나머지를 구함 MOD(4,3) // 1
SIZE(컬렉션 값 연관 경로식) 컬렉션의 크기를 구함 SIZE(t.members) // n
     
CURRENT_DATE 현재 날짜 SELECT CURRENT_DATE FROM Team t // 2021-01-01
CURRENT_TIME 현재 시간 SELECT CURRENT_TIME FROM Team t // 18:30:21
CURRENT_TIMESTAMP 현재 날짜 시간 SELECT CURRENT_TIMESTAMP FROM Team t
// 2021-01-01 18:30:21.711

 

- CASE식

키워드 설명 예제
CASE 특정 조건에 따른 분기 CASE
    WHEN [조건식] THEN [스칼라식]
    WHEN [조건식] THEN [스칼라식]
    ....
    ELSE [스칼라식]
END
COALESCE 스칼라식을 차례대로 조회해서 NULL이 아니면 반환 SELECT COLALESCE(m.username, '이름없음') FROM Member m
NULLIF 두 값이 같으면 NULL을 반환하고 다르면 첫 번째 값을 반환 SELECT NULLIF(m.username, '관리자') FROM Member m

다형성 쿼리

https://xggames.tistory.com/47 - 상속관계 매핑 참조

- JPQL로 부모 엔티티를 조회하면 그 자식 엔티티도 함꼐 조회한다. 그리고 전략(InheritanceType)에 따라 수행되는 SQL이 달라진다.

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

ex) SELECT i FROM Item i WHERE type(i) IN (Book, Movie)

JPQL 조인

- SQL문법과 유사하지만, 문법이 조금 다르며, 기능은 같다.

- 연관 필드(연관 관계를 가지기 위해 사용하는 필드)를 사용한다.

 

- 내부조인(INNER JOIN)

[INNER] 생략가능

// 정상코드
String jpql = "SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = 'xggames_team'";
// 오류코드
// String jpql = "SELECT m FROM Member m INNER JOIN Team t WHERE t.name = 'xggames_team'";

// TypedQuery를 사용할 수 없음. 따라서 Object[]로 받는다.
String jpql = "SELECT m, t FROM Member m INNER JOIN m.team t WHERE t.name = 'xggames_team'";
List<Object[]> result = em.createQuery(jpql).getResultList();

 

- 외부조인(LEFT OUTER JOIN)

[OUTER] 생략가능

JPA 2.1 버전부터는 조인할때 ON절을 지원한다.

// 외부 조인
String jpql = "SELECT m FROM Member m LEFT OUTER JOIN m.team t WHERE t.name = 'xggames_team'";

// ON절 사용 예
String jpql = "SELECT m FROM Member m LEFT JOIN m.team t ON t.name = 'xggames_team'";

 

- 컬렉션 조인

컬렉션을 사용하는 연관필드로 조인

String jpql = "SELECT t, m FROM Team t LEFT JOIN t.members m";

 

- 세타 조인

WHERE 절을 사용해서 세타 조인을 할 수 있다. 내부 조인만 지원하며, 관계없는 엔티티도 조인할 수 있다.

String jpql = "SELECT COUNT(m) FROM Member m, Team t WHERE m.username = t.name";

 

- 페치 조인

페치 조인은 일반적인 SQL 조인 종류는 아니고, JPQL에서 성능 최적화를 위해 제공하는 기능이다.

=> 일반 조인은 결과를 반환할 때 연관관계까지 고려하지 않는다. SELECT의 프로젝션(엔티티)들만 조회한다.

FetchType.Lazy(지연로딩)인 경우에는 팀엔티티만 조회를하고, 회원컬렉션 엔티티는 호출되는 시점에 쿼리를 실행한다.

FetchType.EAGER(즉시로딩)인 경우에는 팀엔티티를 조회하고, 즉시 회원컬렉션을 조회하는데, 각각의 SQL로 호출을 하기때문에 총 2회의 SQL을 실행한다.

그러나 페치조인으로 호출하게 되는 경우, 연관된 엔티티도 한번에 조회하기 때문에 성능 최적화를 할 수 있다.

 

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

    - 패치 조인문법 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH

    - 글로벌 로딩 전략 FetchType.LAZY + 페치 조인 으로 성능 최적화를 꾀할 수 있다.

    (글로벌 로딩 전략을 FetchType.EAGER 으로 설정하는경우, 일부는 빠를 수 있지만 엔티티를 자주 로딩하므로 성능에 악영향을 줄 수 있다)

    - 페치 조인은 연관된 엔티티를 쿼리 시점에 조회하므로 지연 로딩이 발생하지 않으며 따라서 준영속 상태에서도 객체 그래프를 탐색할 수 있다.

// EntityManager생성..

// -- 페치 조인 --
String jpql = "SELECT m FROM Member m JOIN FETCH m.team";

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

for (Member member : members) {
    // 페치 조인으로 회원과 팀을 함께 조회해서 지연 로딩 발생 안함!
    member.getUsername();
    member.getTeam().name();
}

// -- 컬렉션 페치 조인 --
String jpql2 = "SELECT t FROM Team t JOIN FETCH t.members WHERE t.name = 'xggames_team'";

List<Team> teams = em.createQuery(jpql2, Team.class).getResultList();

for (Team team : teams) {
    team.getName();
    // 페치 조인으로 팀과 회원을 함꼐 조회해서 지연 로딩 발생 안함!
    for (Member member : team.getMembers()) {
        member.getUserName();
        member;
    }
}

    - 페치 조인의 DISTINCT(중복제거)

    : JPQL에서 DISTINCT는 SQL에서 DISTINCT를 처리함은 물론이고, 애플리케이션 영역에서 한번 더 중복을 제거한다.

    - 페치 조인의 한계점

        - 페치 조인은 JOIN절 뒤에 Alias를 사용할 수 없다.

        - 둘 이상의 컬렉션을 FETCH할 수 없다. 컬렉션 x 컬렉션의 카테시안 곱이 만들어지기 때문이다.

        - 컬렉션을 페치 조인하면, 페이징 API를 사용할 수 없다.

        (반대로 컬렉션(일대다)가 아닌 단일 값 연관 필드(일대일, 일대다)들은 페이징 API를 사용할 수 있음)

        https://javabom.tistory.com/104

Named 쿼리: 정적 쿼리

- 동적 쿼리: em.createQuery("SELECT m FROM....~");과 같이 JPQL을 문자로 완성해서 직접 넘기는 것

- 정적 쿼리(=Named 쿼리): 미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용

- XML로도 정의할 수 있다. (아래 링크를 참조하자)

 

(JPA,SpringData) Named Query 사용하기

 

jistol.github.io

- NamedQuery 예제

@Entity
@NamedQuery(
    name = "MemberFindByUsername",
    query = "SELECT m from Member m WHERE m.username = :username"
)
public class Member {
    // ...
}

// EntityManager 추가..

List<Member> resultList = em.createNamedQuery("MemberFindByUsername", Member.class)
    .setParameter("username", "xggames")
    .getResultList();
    
// NamedQuery 여러개 만들기
@NamedQueries({
    @NamedQuery(
        name = "MemberFindByUsername",
        query = "SELECT m from Member m WHERE m.username = :username"
    ),
    @NamedQuery(
        name = "MemberFindByUserage",
        query = "SELECT m from Member m WHERE m.age = :userage"
    )
})

- NamedQuery의 속성 설명

속성 설명
name Named 쿼리이름 (필수)
query JPQL 정의 (필수)
lockMode 쿼리 실행시 락모드를 설정할 수 있다. (default: NONE)
hints JPA 구현체에 쿼리 힌트를 줄 수 있다. (2차캐시)

벌크연산

: 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 한다. executeUpdate() 메소드를 사용한다. 이 메소드는 벌크 연산으로 영향을 받은 엔티티 건수를 반환한다. 벌크연산은 2차 캐시를 무시하고 DB에 직접 실행하므로 영속성 컨텍스트와 DB사이에 차이가 발생할 수 있으므로 주의해야한다.

- 벌크연산으로 인한 영속성 컨텍스트와 DB에 정보차이 현상 해결하는 방법

1] em.refresh() 사용: 벌크연산 이후에 DB의 엔티티를 리셋하여 DB로부터 관리중인 모든 엔티티를 다시 읽어오게한다.

2] 벌크연산 먼저 실행: 데이터를 조회하기전에 벌크연산을 먼저 실행한다.

3] 벌크연산 수행 후 영속성 컨텍스트 초기화: 초기화를 안하면 영속성 컨텍스트에 남아 있는 엔티티를 조회할 수 있는데, 이 엔티티는 벌크연산이 적용되어있지 않다. 이때 초기화(em.clear())를하면 이후 엔티티를 조회할 때 벌크연산이 적용된 DB에서 엔티티를 조회한다.

참고자료

- 서적 - 자바 ORM 표준 JPA 프로그래밍 제 10장 - 김영한 지음

- https://devlogofchris.tistory.com/29 (카테시안  곱)

- https://jistol.github.io/spring/2017/11/06/jpa-namedquery/ (NamedQuery - XML구현예제)

'SpringFramework > JPA' 카테고리의 다른 글

JPA - QueryDSL  (0) 2021.11.19
JPA - Criteria  (0) 2021.11.19
JPA - 값타입  (0) 2021.11.16
JPA - 연관관계 고급매핑  (0) 2021.11.15
JPA - 연관관계  (0) 2021.11.14