ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 영속성 컨텍스트
    Java/JPA 2020. 10. 23. 01:31

    영속성 컨텍스트(Persistence Context)란?

    영속성 컨텍스트는 엔티티를 영구 저장하는 환경을 뜻한다.

    애플리케이션과 데이터 베이스 사이에서 레이어(영속성 컨텍스트)를 두어 엔티티 객체를 관리하기 위한 공간으로 사용된다.

    영속성 컨텍스트는 엔티티를 관리한다.

     

    영속성 컨텍스트를 조금 더 알기 위해서는 몇가지를 알고 넘어가야 한다.

     

     

    엔티티 매니저란?

    엔티티 매니저는 엔티티의 CRUD와 같이 엔티티와 관련된 모든 작업들을 처리하는 관리자다.

    JPA는 매 요청마다 EntityManagerFactory에서 EntityManager를 생성해주고, DB 커넥션 풀을 사용하여 데이터 베이스에 연결한다.

     

     

    * 엔티티 매니저는 여러 쓰레드가 하나의 엔티티 매니저에 접근하면 동시성 문제가 발생한다!!!   는데 무슨 이야기일까..?🤔

    외부로부터 요청이 발생하면 엔티티 매니저 팩토리에서 엔티티 매니저를 각각의 요청(스레드)에 하나씩 생성해준다.

    엔티티 매니저가 요청 당 하나씩 매핑되는 이유는 스레드 세이프하지 않기 때문이다.

    여러 쓰레드가 하나의 자원을 동기화 없이 사용하려고 시도하는 상황, 즉 Race condition 발생하게 되어 데드 락이 발생한다.

     

    참고링크

     

    JPA EntityManager와 동시성

    필자는 JPA 프로그래밍 책을 학습 하던 중 EntityManager를 여러 스레드에서 동시에 접근하면 동시성(Concurrency) 문제가 생긴다는 문장을 읽었다.

    medium.com

     

     

     

    엔티티의 생명주기

    영속성 컨텍스트에 저장되는 엔티티는 다음과 같은 생명주기를 가진다.

     

    비영속 (transient)

    엔티티 객체가 생성되었으나, 영속성 컨텍스트에 속해있지 않은 상태

    ex) 엔티티 객체를 생성만 수행하고 쿼리 관련 기능을 수행하지 않는 상태

     

     

    영속 (managed)

    영속성 컨텍스트가 엔티티를 지속적으로 관리하는 상태

    ex) 조회 기능을 통해 엔티티 객체를 생성하거나 저장 등 쿼리 기능을 수행한 상태

    엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장하는 경우 '캐시', '쓰기 지연 SQL 저장소 지원' 등 영속성 컨텍스트의 관리를 받게된다.

    (영속 상태가 된다고 해서 데이터 베이스에 저장되지는 않는다!!!)

     

    준영속 (detached)

    영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않게 되면 준영속 상태

    ex) 쿼리하던 객체를 메모리에서 할당 해제하거나 영속성 컨텍스트 영역을 초기화 및 해제한 상태

    엔티티가 준영속 상태가 되면 영속 상태에서 진행되었던 변경 사항들이 트랜잭션에서 삭제되어 데이터 베이스에 반영되지 않는다.

     

    삭제 (removed)

    영속성 컨텍스트와 데이터 베이스로부터 엔티티를 삭제

    ex) 엔티티에 대해 삭제 쿼리를 수행한 상태

     

     

    영속성 컨텍스트의 장점

    엔티티가 영속성 컨텍스트의 관리를 받게 되면 다음과 같은 장점을 얻게 된다.

     

    1차 캐시

    영속성 컨텍스트는 내부에 캐시를 가지고 있는데, 영속 상태의 엔티티들은 모두 이곳에 저장된다.

    Member member = new Member();
    member.setId("member1");
    member.setUsername("회원1");
    
    //1차 캐시에 저장
    em.persist(member);
    
    //1차 캐시에서 조회
    Member findMember = em.find(Member.class, "member1");

    위의 코드는 다음과 같은 절차로 수행된다.

    1. 1차 캐시에서 식별자 값(id)로 엔티티를 찾는다.
    2. 엔티티가 1차 캐시에 존재하면 메모리에 있는 1차 캐시에서 엔티티를 조회하여 가져온다.
    3. 만약 1차 캐시에 엔티티가 존재하지 않으면 데이터 베이스에서 조회 후 1차 캐시에 저장한 다음 1차 캐시의 데이터를 가져온다.

    2. 1차 캐시에서 엔티티를 조회한다.
    3. 만약 1차 캐시에 엔티티가 존재하지 않으면 데이터 베이스에서 조회하여 1차 캐시에 저장 후 데이터를 조회한다.

     

     

    영속 상태의 엔티티 동일성 보장

    Member a = entityManager.find(Member.class, "member1");
    Member b = entityManager.find(Member.class, "member1");
    
    System.out.println(a == b);

    위와 같은 코드가 있다고 가정해보자.

    엔티티 매니저를 통해 member1이라는 데이터를 조회해서 a와 b로 저장한다.

    이 때, a와 b는 서로 같은 인스턴스일까?

     

    정답은  true다.

    영속성 컨텍스트는 1차 캐시에 저장되어 있는 동일한 엔티티 인스턴스를 반환하기 때문이다.

    즉, 엔티티의 동일성을 보장한다.

     

    그럼 만약 영속성 컨텍스트에 member1 데이터가 없는 상태에서 위의 코드가 수행되면 결과는 어떻게 될까?

    엔티티 매니저는 1차 캐시로부터 member1 데이터를 찾고, 없는 경우 데이터 베이스에서 member1를 조회하여 1차 캐시에 저장 후 반환한다. 

    그렇기 때문에 두번째 find() 요청이 발생할 때에는 영속성 컨텍스트 1차 캐시에 저장되어 있는 member1 인스턴스를 반환하므로 a와 b는 동일한 인스턴스가 된다.

     

     

    쓰기 지연 SQL 저장소

    영속성 컨텍스트는 내부에 'SQL 쿼리 저장소'를 가지고 있다.

    엔티티 매니저를 통하여 영속성 컨텍스트에 엔티티를 저장, 수정, 삭제 등의 행위를 수행하면 JPA를 통해 SQL 쿼리가 만들어지게 되는데 이때 데이터 베이스에 바로 반영되지 않고, 쿼리 저장소에 SQL 쿼리들을 저장해둔다.

    그리고 최종적으로 영속성 컨텍스트를 flush(트랜잭션 commit)하면 데이터 베이스에 작업 내역을 반영하게 된다.

    엔티티를 persist하여 1차 캐시에 저장하고, SQL 쿼리를 쓰기 지연 SQL 저장소에 기록한다.
    commit이 발생하면 쓰기 지연 SQL 저장소에 기록된 쿼리들을 데이터 베이스로 보낸다.

     

     

    엔티티 상태 변경 감지 (Dirty Checking)

    EntityTransaction transaction = entityManager.getTransaction();
    transaction.begin() // 트랜잭션 시작
    
    // 영속 엔티티 조회
    Member memberA = entityManager.find(Member.class, "memberA");
    
    // 영속 엔티티 데이터 수정
    memberA.setUsername("hi");
    memberA.setAge(10);
    
    transaction.commit(); // 트랜잭션 커밋

    위의 코드를 보자.

    엔티티 매니저를 통해 영속성 컨텍스트에서 memberA라는 정보를 조회하여 memberA 객체를 생성했다.

    그리고 객체의 username과 age 정보를 setter를 통해 수정했다.

    이후 entityManager.update() 행위 없이 transaction을 commit하기만 했는데도 데이터 베이스에 memberA 정보의 username과 age 정보가 갱신된다.

     

    어떻게 그게 가능한 것일까?

    트랜잭션이 commit되면 1차 캐시에 저장된 엔티티 스냅샷 정보와 비교하여 변경된 정보가 있는 경우 UPDATE 쿼리를 생성하여 데이터 베이스에 전달한다.

    1. 트랜잭션을 commit한다. (그럼 EntityManager.flush()가 호출된다.)
    2. '1차 캐시'에 저장되어 있는 스냅샷 정보와 내가 방금 수정 작업한 엔티티 정보를 비교하여 변경 사항을 찾는다.
    3. 변경사항이 있는 경우 JPA가 수정 쿼리를 생성하여 '쓰기 지연 SQL 저장소'에 기록한다.
    4. 쓰기 지연 저장소의 SQL을 데이터 베이스로 전달한다.
    5. 데이터 베이스에 트랜잭션을 commit한다.

     

     

    참고 링크: 

    - 1 -

    - 2 -

    - 3- 

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

    격리 전략  (0) 2020.10.25
    트랜잭션, 동시성  (0) 2020.10.25
    JPA(Java Persistence API)  (0) 2020.08.07
Designed by Tistory.