BackEnd/JPA

[JPA]JPA와 영속성 컨텍스트

dev seon 2024. 12. 17. 18:19

 

 

JPA는 객체와 데이터베이스 사이에서 데이터를 효율적으로 관리해주는 기술입니다.

이때 핵심 역할을 하는 것이 영속성 컨텍스트입니다.

영속성 컨텍스트는 엔티티를 저장하고 관리하는 메모리 공간으로, 객체의 상태를 추적하고 데이터베이스와의 동기화를 돕습니다.

이 글에서는 영속성 컨텍스트의 개념과 엔티티의 생명주기, 그리고 1차 캐시, 변경 감지, 쓰기 지연 등 주요 특징을 정리해보겠습니다.

01 엔티티 매니저 팩토리와 엔티티 매니저

지난번 JPA를 시작하며 엔티티 매니저 팩토리를 통해 엔티티 매니저를 생성하는 과정을 거쳤습니다.

//엔티티 매니저 팩토리 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");

//엔티티 매니저 생성
EntityManager em = emf.createEntityManager();

엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유해도 되지만,

엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생할 수 있어 스레드 간에 절대 공유하면 안 됩니다.

또한, 엔티티 매니저는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않지만,

엔티티 매니저 팩토리는 생성할 때 커넥션풀이 만들어집니다.

02 영속성 컨텍스트란?

영속성 컨텍스트는 JPA의 핵심 개념으로, 엔티티를 저장하고 관리하는 메모리 공간입니다.

이를 통해 엔티티의 생명주기상태 관리가 이루어집니다.

03 엔티티의 생명주기

엔티티의 생명주기는 크게 4가지 단계로 나눌 수 있습니다.

  • 비영속 : 영속성 컨텍스트와 전혀 관계가 없는 상태
    • 단순히 객체가 생성된 상태
  • 영속 : 영속성 컨텍스트에 저장된 상태
    • 영속성 컨텍스트에 의해 관리되는 상태
    • find나 JPQL을 사용해서 조회한 엔티티, 저장한 상태 등
  • 준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태
    • em.detach(엔티티) 해당 엔티티를 영속성 컨텍스트에서 분리
    • em.close(), em.clear() 호출로 영속성 컨텍스트를 초기화하면 준영속 상태가 됨
  • 삭제 : 삭제된 상태

04 영속성 컨텍스트의 특징

엔티티 조회

  • 1차 캐시 : 영속성 컨텍스트 내부 캐시에 영속 상태 엔티티가 저장됩니다.
    • 1차 캐시의 키는 식별자의 값으로 데이터베이스의 기본 키와 매핑되어 있습니다.
    • 따라서 같은 식별자의 엔티티를 여러 번 조회하면 동일한 객체(인스턴스)가 반환되어 동일성(==)이 보장됩니다.
  • em.find(엔티티 클래스 타입, 엔티티 식별자 값)
    • 이 메서드를 호출하면 1차 캐시에서 엔티티를 먼저 찾고 거기에 없을 경우 데이터베이스에서 조회합니다.
    • 데이터베이스를 조회해 엔티티를 생성한 후 1차 캐시에 저장한 후에 영속 상태의 엔티티를 반환합니다.
  • 영속 엔티티의 동일성 보장 → 반복해서 호출해도 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환합니다.

엔티티 등록

  • 트랜잭션을 지원하는 쓰기 지연 : 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 모아둡니다.
  • 쓰기 지연은 트랜잭션의 성능 최적화를 위해 사용됩니다. 여러 번 persist()를 호출해도 즉시 SQL이 실행되지 않고 쿼리 저장소에 모아뒀다가 한 번에 데이터베이스로 전송되기 때문입니다.
  • 트랜잭션 커밋 직전에만 데이터베이스에 SQL을 전달하면 됩니다.

엔티티 수정

  • SQL 수정 쿼리를 사용할 경우 보통 각각 수정 쿼리를 따로 작성하므로 수정 쿼리가 많아집니다.
  • JPA의 경우 단순히 엔티티를 조회해서 데이터만 변경하면 됩니다.
  • 변경 감지 : 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능으로 영속 상태의 엔티티에만 적용됩니다.
  • 스냅샷 : JPA는 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 복사해서 저장합니다.
    • 스냅샷은 엔티티의 초기 상태를 복제해서 저장해 둔 것입니다. JPA는 트랜잭션 커밋 시점에 스냅샷과 현재 엔티티의 상태를 비교하여 변경된 필드를 찾고 SQL 쿼리를 생성합니다.
  • 트랜잭션 커밋 후 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾습니다.
  • 기본적으로 모든 필드를 업데이트 합니다.
    • 수정 쿼리가 항상 같습니다.
    • 동일한 쿼리를 보내면 이전에 파싱된 쿼리를 재사용할 수 있습니다.
  • 그러나 컬럼이 30개 이상이 되면 @DynamicUpdate와 같은 동적 수정 쿼리가 빠릅니다.

엔티티 삭제

  • 엔티티 등록과 비슷하게 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록합니다.
  • 트랜잭션을 커밋해서 플러시를 호출하면 실제 데이터베이스에 삭제 쿼리를 전달합니다.
  • em.remove(엔티티)를 호출하는 순간 영속성 컨텍스트에서 제거됩니다.

05 플러시

  • 플러시란 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것입니다.
  • 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾습니다.
  • 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록합니다.
  • 이후 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다.
  • 플러시 방법
    • 직접 호출 : em.flush();
    • 트랜잭션 커밋 시 플러시 자동 호출
    • JPQL 쿼리 실행 시 플러시 자동 호출
  • 플러시만 호출하면 SQL 쿼리만 데이터베이스로 전송될 뿐 트랜잭션이 커밋되는 것은 아닙니다
  • 커밋 시점에 플러시가 자동으로 실행되고 변경 사항이 데이터베이스에 반영된 후 트랜잭션이 완료됩니다.

플러시 모드 옵션

플러시 모드를 직접 지정하려면 아래와 같이 설정할 수 있습니다.

javax.persistence.FlushModeType.AUTO //커밋 + 쿼리 실행 (기본값)
javax.persistence.FlushModeType.COMMIT //커밋할 때만

06 준영속

  • 영속성 컨텍스트가 제공하는 기능을 사용할 수 없음
  • 준영속 상태로 만드는 방법
    • em.detach(엔티티) : 특정 엔티티만 준영속 상태로 전환
      • 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 엔티티 관리를 위한 모든 정보가 제거됩니다.
    • em.clear() : 영속성 컨텍스트 초기화
    • em.close() : 영속성 컨텍스트 종료
  • 특징
    • 거의 비영속 상태에 가까움 : 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않습니다.
    • 식별자 값을 가지고 있습니다.
    • 지연 로딩을 할 수 없습니다.

병합

  • 준영속 상태의 엔티티를 다시 영속 상태로 변경합니다.
  • em.merge(엔티티) 를 호출하면 준영속 상태의 엔티티를 받아서 새로운 영속 상태의 엔티티를 반환합니다.
  • 비영속 엔티티도 영속 상태로 만들 수 있습니다.
  • save or update 기능을 수행합니다.