관리 메뉴

공부 기록장 💻

[Spring/JPA] 영속성 관리 (엔티티의 생명 주기, 영속성 컨텍스트가 제공하는 기능들 - transaction commit, dirty check, write-behind) 본문

카테고리 없음

[Spring/JPA] 영속성 관리 (엔티티의 생명 주기, 영속성 컨텍스트가 제공하는 기능들 - transaction commit, dirty check, write-behind)

dream_for 2023. 4. 24. 03:20

자바 ORM 표준 JPA 프로그래밍 - 기본편(인프런) 참고

 

 


 

 

JPA에서 가장 중요한 2가지

JPA 기술을 사용할 때 중요한 2가지는 다음과 같다.

1. 객체와 관계형 데이터베이스 매핑하기 

정적인 설계의 관점에서 객체를 생성하고, 관계형 DB와 매핑하는 것이 첫번째로 고려해야 할 점이고,

 

2. 영속성 컨텍스트 

다음으로 JPA의 내부 동작 원리에 대한 이해를 바탕으로 영속성을 관리하는 것이 두번째로 고려해야 할 점이다.

 

영속성 컨텍스트가 무엇이고, 영속성을 관리한다는 개념이 무엇인지 살펴보자!

 

 

 

 


EntityManagerFactory와 EntityManager

EntityManagerFactory룰 통해 고객이 요청이 올 때마다 EntityManager 객체를 생성하고,

EntityManager은 내부적으로 Connection Pool의 각 Connection을 사용해 DB와 연결을 하게 된다.

 

 

"영속성 컨텍스트"란?

영속성 컨텍스트는, JPA를 이해하는데 있어 가장 중요한 용어이다.

쉽게 풀어보면 "엔티티를 영구 저장하는 환경" 이라는 뜻이다.

EntityManager.persist(entity) 는 영속성 컨텍스트를 통해 entity를 영속화 한다는 의미인데,

persist 메서드는 DB에 저장하는 것이 아니라, entity를 영속성 컨텍스트에 저장한다는 뜻이다.

 

영속성 컨텍스트는, 논리적인 개념이다.

이는 JPA가 EntityManager을 통해 영속성 컨텍스트에 접근한다는 의미이다.

(데이터를 저장할 때, 1차 캐시에 먼저 저장한 후에 commit 이 수행될 때 DB에 최종 저장을 하거나, 데이터를 접근할 때 DB에서 데이터를 가져와 1차 캐시에 올려놓을 때, 1차 캐시에서 관리하게 되는 객체들을 JPA과 관리하는 영속성 컨텍스트라고 이해해도 된다.)

 

EntityManager를 생성하면, PersistenceContext(영속성 컨텍스트)를 1:1로 생성하게 된다.

 

 

 

엔티티의 생명주기

 

1) 비영속 (new/transient)

영속성 컨텍스트와 전혀 관계가 없는 새로운 상태 (em.persist())의 경우)

 

2) 영속 (managed)

영속성 컨텍스트에 관리 되는 상태

 

3) 준영속 (detached)

영속성 컨텍스트에 저장되었다가 분리된 상태

 

4) 삭제 (removed)

삭제된 상태

 

 

 

비영속 상태

 

 

영속 상태

 

준영속 상태와 삭제 상태

준영속 상태는 영속성 컨텍스트에서 분리하는 것을 말하고,

삭제의 경우 실제 DB에서 객체를 삭제하는 것을 말한다.

 

 

아래 예시의 경우,

em.persist(member)  상태는 영속 상태가 되고 (DB에 아직 저장 x),

실제 DB에 쿼리문을 날려 데이터를 저장하는 때는 commit을 날릴 때이다.

 

@Test
void EM_test(){
    // DB 연결
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

    EntityManager em = emf.createEntityManager();

    EntityTransaction tx = em.getTransaction();
    tx.begin();

    try {
        // 비영속
        Member member = Member.builder()
                .email("lyb2325@gmail.com").password("1234").build();
        // 영속
        em.persist(member);

        tx.commit();
    }catch (Exception e){
        tx.rollback();
    }finally {
        em.close();
    }
    emf.close();;
}

 

 


 

영속성 컨텍스트의 이점

 

1. 엔티티 조회 및 1차 캐시 기능 제공

 

아래와 같이 두 상황이 있다.

 

영속성 컨텍스트의 경우 트랜잭션 단위에 대해 수행하는데,

만약 한 트랜잭션 내에서 다음과 같이 em.persist()를 이용해 Id (PK) 값이 member 1인 회원 객체를 저장하고 나서,

해당 회원을 조회하는 기능이 있는 경우 DB에 접근하여 해당 객체를 찾는 것이 아니라

1차 캐시에 저장된 값에 접근하여 객체를 가져오게 된다.

 

 

그러나 트랜잭션 내에서 영속 객체를 저장한 것이 아니라면,

1차 캐시에 member2 는 저장되어 있지 않기 때문에 DB 조회를 수행하게 된다. 

이 때 찾고자 했던 member2 객체는 DB에서 조회한 후에, 1차 캐싱을 수행한 후에 가져오게 된다.

 

 

 

그러나 사실상 1차 캐시는 작은 한 트랜잭션 단위 내에서 수행되는 것이기 때문에 아주 큰 성능 최적화 효과를 보기는 어렵다.

 

 

 

2. 영속 엔티티의 동일성 보장 ( == 비교)

자바 Collections를 사용하여 동일한 두 객체를 비교하는 경우, 주소가 같기 때문에 동일한 객체로 인식하는 것과 마찬가지로

JPA는 == 연산자를 이용한 비교 연산의 경우, 영속 엔티티의 동일성을 보장한다.

 

이것이 가능한 이유는?

1차 캐시 기능 때문이다. 이는 같은 트랜잭션 내에서, 트랜잭션의 격리 수준을 반복 가능한 읽기(Repetable Read) 등급의 수준을 DB가 아닌, 애플리케이션 차원에서 제공하기 때문이다. 

Repeatable Read란?

더보기

Repeatable Read (반복 가능한 읽기) 는 MySQL의 InnoDB 스토리지 엔에서 기본적으로 발생하는 격리 수준(트랜잭션 격리 수준 Level 3)으로, 한 트랜잭션 내에서 특정 행을 조회시 항상 같은 데이터를 응답하는 것을 보장하는 격리 수준이다. 가장 높은 격리 수준인 Serializable과 다르게 중간에 행이 추가되는 것을 막지 않기 때문에, 이로 인해 Phantom Read 현상이 발생할 수 있다.

 

 

 

 

3. 엔티티 등록 시, 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)

JPA는 한 트랜잭션이 commit 될 때까지는 INSERT 쿼리를 DB에 보내지 않는다.

commit 하는 순간, transaction 내에 persist()로 영속 상태가 되었던 객체를 한꺼번에 DB에 저장하게 된다.

(이는 위에서 언급한 Repeatable Read와도 연관 된다.)

 

 

아래의 예시를 살펴보자.

em.persist(memberA) 가 실행되면, memberA는 1차 캐시에 저장이 되고 INSERT SQL을 생성하여 쓰기 지연 SQL 저장소에 쌓아둔다.

다음으로 em.persist(memberB) 가 실행되면, 마찬가지로 1차 캐시에 memberB를 저장하고 쓰기 지연 SQL저장소에 쌓아둔다.

 

 

이후 commit() 이 발생하는 시점에, 쓰기 지연 SQL 저장소에 쌓아뒀던 쿼리들이 flush가 수행되면서 실제 데이터베이스를 대상으로 해당 쿼리들을 실행하게 된다. 

 

 

4. 엔티티 수정 - 변경 감지 (Dirty checking)

다음과 같이 트랜잭션 내에서 memberA를 조회(1차 캐시에 저장)한 후에 해당 영속 엔티티 데이터를 수정하는 상황이 있을 때 자동적으로 변경을 감지하여 update 작업을 자동적으로 해준다.

 

 

위의 동작 원리를 자세히 살펴보자.

데이터 수정이 발생한 트랜잭션에 대한 commit 이 실행되면,

먼저 1차 캐시에서 값을 읽어온 최초 시점의 상태를 스냅샷으로 저장해두고, 변경한 Entity의 값과 각각 비교하여

Entity 변경이 발생하였을 대 쓰기 지연 SQL 저장소에 UPDATE 쿼리를 만들어 둔 다음에,

데이터베이스에 최종적으로 반영을 하게 된다. 

 

 

 

엔티티 삭제 (em.remove()) 도 마찬가지로, commit 시점에 실제 DB의 데이터가 삭제된다.

 

 

 

 

 

728x90
반응형
Comments