API/JPA

객체지향 쿼리 심화

시뻘건 튼튼발자 2024. 9. 8. 11:20
반응형

1. 벌크 연산

엔티티를 수정하려면 영속성 컨텍스트의 변경 감지 기능이나 병합을 사용하고, 삭제하려면 remove() 메소드를 사용한다. 만약 수백개 혹은 수천개의 데이터를 처리해야한다면 성능이 매우 떨어질 것이다. 이럴 때 벌크 연산을 사용하여 한 번에 처리가 가능하다.

1.1 벌크 연산의 주의점

벌크 연산은 데이터베이스에서 대량의 데이터를 한 번에 업데이트하거나 삭제하는 작업을 의미한다. JPA에서는 JPQL을 사용하여 벌크 연산을 수행할 수 있다. 벌크 연산은 다음과 같은 주의점을 가지고 있다.

  • 영속성 컨텍스트의 비동기성: 벌크 연산은 데이터베이스에 직접 쿼리를 실행하기 때문에, JPA의 영속성 컨텍스트에 있는 엔티티들과 동기화되지 않는다. 즉, 영속성 컨텍스트는 벌크 연산 결과를 알지 못하므로, 벌크 연산 후에는 영속성 컨텍스트를 명시적으로 비우거나 새로 고쳐야 할 수 있다. 그렇지 않으면 일관성 문제가 발생할 수 있다.
  • 벌크 연산의 동시성 문제: 여러 스레드가 동시에 벌크 연산을 수행할 경우, 데이터베이스의 동시성 문제가 발생할 수 있다. 이를 방지하기 위해 트랜잭션 격리 수준을 적절히 설정하거나 데이터베이스의 잠금 전략을 고려해야 한다.
  • 엔티티 상태 관리: 벌크 연산은 엔티티의 상태를 직접 수정하기 때문에, 엔티티의 상태가 영속성 컨텍스트에 있는 경우에는 상태가 일치하지 않을 수 있다. 따라서, 벌크 연산 후에는 영속성 컨텍스트를 초기화하거나 갱신하는 것이 좋다.
  • 성능 고려: 벌크 연산은 많은 양의 데이터를 처리하므로 성능에 미치는 영향이 크다. 따라서 인덱스와 쿼리 최적화, 배치 처리를 통해 성능을 개선할 수 있다.

2. 영속성 컨텍스트와 JPQL

JPA에서 JPQL 쿼리를 사용하여 조회한 결과는 두 가지로 나뉜다.

  • 영속 상태인 것: JPQL의 조회 대상이 엔티티인 경우
  • 영속 상태가 아닌 것: JPQL의 조회 대상이 값 타입 혹은 임베디드 타입인 경우

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

JPQL로 조회한 엔티티는 기본적으로 영속성 컨텍스트와 연관된다. JPQL은 영속성 컨텍스트에 저장된 엔티티를 조회하거나, 새로운 엔티티를 조회하여 영속성 컨텍스트에 추가할 수 있다. 조회된 엔티티는 영속성 컨텍스트의 1차 캐시에 저장되어, 이후의 변경 사항이 자동으로 데이터베이스에 반영될 수 있도록 관리된다. 하지만 JPQL 쿼리가 new 키워드를 사용하여 DTO 객체를 반환하거나, 명시적으로 EntityManager를 통해 detach() 메서드를 호출하면 영속성 컨텍스트와의 연결이 끊어질 수 있다.

2.2 find() vs JPQL

find()는 영속성 컨텍스트의 1차 캐시에서 엔티티를 먼저 찾고 없을 때 데이터베이스로부터 조회를 한다. 반면에 JPQL은 데이터베이스에서 먼저 찾고 영속성 컨텍스트의 1차 캐시에 있으면 조회 결과를 버리고 1차 캐시에 있는 엔티티를 반환한다. 단, 1차 캐시에 없다면 JPQL로 조회한 엔티티는 영속 상태가 된다. 왜 그럴까? 우리는 아래와 같이 3가지를 고민해볼 필요가 있다.

  1. 새로운 엔티티를 영속성 컨텍스트에 하나 더 추가한다.
  2. 기존 엔티티를 새로 검색한 엔티티로 대체한다.
  3. 기존 엔티티는 그대로 두고 새로 검색한 엔티티를 버린다.

1번의 경우에는 같은 기본키 값을 가진 엔티티는 등록할 수 없으니 불가능하다. 2번의 경우 영속성 컨텍스트에 수정 중인 엔티티가 사라질 수 있기 때문에 엔티티 동일성을 보장할 수 없다. 그렇기 때문에 3번으로 동작하는 것이다.

  • find() 메서드: EntityManager.find()는 기본 키(Primary Key)를 사용하여 데이터베이스에서 엔티티를 조회한다. 이 메서드는 항상 영속성 컨텍스트에서 엔티티를 검색하며, 없으면 데이터베이스에서 조회하고 결과를 영속성 컨텍스트에 저장한다. 따라서 find()는 영속성 컨텍스트에 저장된 엔티티를 반환하며, 데이터베이스에 대한 직접적인 쿼리가 발생한다.
  • JPQL: JPQL 쿼리는 보다 복잡한 조건으로 데이터를 조회할 수 있다. JPQL은 엔티티 객체를 직접 조회할 수 있으며, 특정 조건에 맞는 엔티티를 검색한다. JPQL을 사용할 때는 영속성 컨텍스트의 상태에 영향을 미치지 않지만, 쿼리 결과는 영속성 컨텍스트에 저장될 수 있다.

3. JPQL과 플러시 모드

3.1 플러시 모드와 최적화

플러시 모드는 JPA에서 영속성 컨텍스트의 변경 사항이 데이터베이스에 반영되는 시점을 제어하는 방법이다. 플러시 모드 설정은 성능 및 일관성 측면에서 중요한 역할을 한다.

  • FlushModeType.AUTO: 기본 플러시 모드다. JPA는 쿼리가 실행될 때 자동으로 영속성 컨텍스트의 변경 사항을 데이터베이스에 반영한다. 이 모드는 일관성을 보장하지만, 쿼리 실행 시마다 플러시가 발생할 수 있다.
  • FlushModeType.COMMIT: 트랜잭션이 커밋될 때만 영속성 컨텍스트의 변경 사항을 데이터베이스에 반영한다. 이 모드는 성능을 개선할 수 있지만, 쿼리 실행 전후에 일관성 문제가 발생할 수 있다. 따라서 쿼리 실행 시 데이터베이스의 최신 상태를 보장하지 않을 수 있다.

플러시 모드를 적절히 설정하면 성능 최적화와 데이터 일관성 사이의 균형을 맞출 수 있다. 성능을 고려하여 FlushModeType.COMMIT을 사용하는 경우, 쿼리 실행 전에 필요시 flush() 메서드를 호출하여 영속성 컨텍스트의 변경 사항을 강제로 반영할 수 있다.

반응형