Application/JPA

[JPA] JPQL 객체지향 쿼리 심화

seungh0 2021. 2. 21. 22:40
반응형
JPA에 대해서 헷갈렸던 개념들을 위주로 정리하는 글입니다.

자바 ORM 표준 JPA 프로그래밍

벌크 연산

엔티티를 수정하려면 영속성 컨텍스트의 변경 감지 기능이나 병합을 사용하고, 삭제하려면 EntityManager.remove() 메소드를 사용한다. 

하지만 수백 개 이상의 엔티티를 하나씩 처리하기에는 시간이 너무 오래 걸린다.

 

이럴 때 여러 건을 한 번에 수정하거나 삭제하는 벌크 연산을 사용하면 된다. (executeUpdate() 사용)

// 벌크 연산: 수정
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();
                    
                    
// 벌크 연산: 삭제
String qlString = 
    "delete from Product p" +
    "where p.price < :price";
 
int resultCount = em.createQuery(qlString)
                    .setParameter("price", 100)
                    .excuteUpdate();

 

벌크 연산의 주의점

벌크 연산이 영속성 컨텍스트과 2차 캐시를 무시하고 데이터베이스에 직접 쿼리 한다는 점을 주의해야 한다.

따라서 영속성 컨텍스트와 데이터베이스 간에 데이터 차이가 발생할 수 있으므로 주의해서 사용해야 한다.

 

해결 방법

1. em.refresh()

- 벌크 연산을 수행한 직후에 정확한 상품 A 엔티티들 사용해야 한다면 em.refresh()를 사용해서 데이터베이스에서 상품 A를 다시 조회한다.

 

2. 벌크 연산 먼저 수행

- 가장 실용적인 해결법으로, 벌크 연산을 먼저 실행하고 나서 상품 A를 조회하면 벌크 연산으로 이미 변경된 상품 A를 조회하게 된다.

 

3. 벌크 연산 수행 후 영속성 컨텍스트 초기화

- 벌크 연산을 수행한 직후에 바로 영속성 컨텍스트를 초기화해서 영속성 컨텍스트에 남아 있는 엔티티를 제거하는 방법.

 

 

조회한 엔티티만 영속성 컨텍스트가 관리한다.

select m from Member m // 엔티티 조회 (영속 상태 - 관리O)
select o.address from Order o // 임베디드 타입 조회 (관리 X)
select m.id, m.namer from Member m // 단순 필드 조회 (관리 X)

위와 같이 엔티티가 아니면 영속성 컨텍스트에서 관리되지 않는다. => 변경 감지에 의한 수정이 발생하지 않는다.
물론 엔티티들 조회하면 해당 엔티티가 가지고 있는 임베디드 타입은 함께 수정된다.

 

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

JPQL로 데이터베이스에서 조회한 엔티티가 영속성 컨텍스트에 이미 있으면 JPQL로 데이터베이스에서 조회한 결과를 버리고 대신에 영속성 컨텍스트에 있던 엔티티를 반환한다.

 

왜? 데이터베이스에서 새로 조회한 엔티티를 버리고 영속성 컨텍스트에 있는 기존 엔티티를 반환하는 것일까?

=> 영속성 컨텍스트는 동일성을 보장한다. 동일성을 보장하기 위해서 기존의 엔티티를 반환한다.


(영속성 컨텍스트의 동일성 보장이 헷갈리시면 아래의 글을 확인해주세요)

 

[JPA] 영속성 컨텍스트란?

JPA에 대해서 헷갈렸던 개념들을 위주로 정리하는 글입니다. JPA 내부 동작 원리를 알려면 영속성 컨텍스트를 알아야 함. EntityManagerFactory는 여러 스레드에서 동시에 접근해도 안전하지만, 생성하

willseungh0.tistory.com

 

em.find() vs JPQL

em.find() 메소드는 엔티티를 영속성 컨텍스트에서 먼저 찾고 없으면 데이터베이스에 찾는다. 따라서 해당 엔티티가 영속성 컨텍스트에 있으면 메모리에서 바로 찾으므로 성능상 이점이 있다. (1차 캐시)

반면 JPQL은 항상 데이터베이스에 SQL을 실행해서 결과를 조회한다. JPQL로 조회한 엔티티는 영속 상태이며, 영속성 컨텍스트에 이미 존재하는 엔티티가 있으면 기존 엔티티를 반환한다.

 

플러시 모드와 최적화

- JPQL 쿼리 실행하면 영속성 컨텍스트를 플러시 한다.

헷갈리시면 아래쪽 영속성 컨텍스트를 플러시 하는 방법 참고
 

[JPA] 영속성 컨텍스트란?

JPA에 대해서 헷갈렸던 개념들을 위주로 정리하는 글입니다. JPA 내부 동작 원리를 알려면 영속성 컨텍스트를 알아야 함. EntityManagerFactory는 여러 스레드에서 동시에 접근해도 안전하지만, 생성하

willseungh0.tistory.com

em.setFlushMode(FlushModeType.COMMIT) // 커밋 시에만 플러시

플러시 모드를 FlushModeType.COMMIT으로 설정하면 트랜잭션을 커밋할 때만 플러시하고 쿼리를 실행할 때 플러시를 자동으로 호출하지 않는다.

따라서 JPA 쿼리를 사용할 때 영속성 컨텍스트에 있지만 아직 데이터베이스에 반영하지 않은 데이터를 조회할 수 없다. 이런 상황은 잘못하면 데이터 무결성에 심각한 피해를 줄 수 있다.

그럼에도 다음과 같이 플러시가 너무 자주 일어나는 상황에 이 모드를 사용하면 쿼리 시 발생하는 플러시 횟수를 줄여 성능을 최적화할 수 있다.

등록()
쿼리() // 플러시
등록()
쿼리() // 플러시
등록()
쿼리() // 플러시
커밋() // 플러시

 

- FlushModeType.AUTO: 쿼리와 커밋할 때 총 4번 플러시 한다.

- FlushModeType.COMMIT: 커밋 시에만 1번 플러시 한다.

반응형