영속성 컨텍스트란?

SANG HUN SHON·2023년 2월 3일
0

[ 엔티티 매니저 팩토리 ]

영속성 컨텍스트를 알아보기전에 엔티티 매니저 팩토리와 엔티티 매니저를 먼저 알아보자.

  • 엔티티 매니저 팩토리 : 엔티티 매니저를 생성하는 Factory이다.
    • 생성 비용이 크기때문에, 하나만 만들어서 애플리케이션내에서 공유한다 (thread-safe).
    • 내부에 데이터베이스 Connection Pool을 생성한다.
    • 애플리케이션이 종료될떄 엔티티 매니저 팩토리를 종료(close)해야 한다.
  • 엔티티 매니저 : 엔티티를 데이터베이스에 동기화하는 기능을 제공한다.
    • 내부에 데이터소스를 유지하면서 데이터베이스와 통신한다.
    • 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않는다.
    • 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드간에 공유하거나 재사용하면 안된다.
    • 사용이 끝난 엔티티 매니저는 반드시 종료(close)해야 한다.

[ 엔티티 매니저 팩토리가 엔티티 매니저를 사용하는 이유 ]

엔티티 매니저 팩토리는 데이터베이스 Connection Pool을 생성하기 때문에, 생성 비용이 크다.
그러나 엔티티 매니저의 생성 비용은 크지 않기 때문에, 엔티티 매니저 팩토리는 필요에 따라 엔티티 매니저를 생성하여 사용한다.

엔티티 매니저 팩토리는 Thread-Safe 하다.
즉 여러 스레드간에 공유되어도 문제가 없지만, 엔티티 매니저는 데이트베이스 커넥션과 밀접한 관계가 있기 때문에 스레드간에 공유하거나 재사용해서는 안된다.


[ 영속성 컨텍스트란 ]

공식홈페이지에서는 아래와 같이 정의한다.

An EntityManager instance is associated with a persistence context. A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed. The EntityManager API is used to create and remove persistent entity instances, to find entities by their primary key, and to query over entities.

영속성 컨텍스트는 엔티티를 저장하고 관리하는 저장소 개념이다.

  • Application와 Database 사이에서 엔티티를 관리하는 논리적인 영역이다.
  • 영속성 컨텍스트는 엔티티 매니저를 생성할 떄 하나 만들어 진다.
  • 엔티티 매니저를 사용해서 영속성 컨텍스트에 엔티티를 보관하고 저장한다.
  • 영속성 컨텍스트가 관리하는 엔티티는 영속(managed) 상태라고 한다.

[ 영속성 컨텍스트 특징 ]

식별자 값

  • 영속성 컨텍스트는 엔티티를 식별자 값(@Id 를 사용하여 테이블의 기본 키(PK)와 매핑한 값)으로 구분한다.
  • 영속 상태인 경우 식별자 값이 반드시 있어야 한다.

장점

  • 1차 캐시
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

[ 1차 캐시 ]

영속성 컨텍스트는 내부에 1차 캐시를 가지고있고, 1차 캐시의 Key는 식별자 값이다.
아래 예제를 통해 1차 캐시에 어떻게 동작하는지 알아보자.

	public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityManager em2 = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        try {
            tx.begin();
            // 1. 엔티티를 생성한 상태(비영속)
            Member member = new Member(1L, "홍길동");
            // 2.엔티티 영속
            em.persist(member);
            tx.commit();
            // 3. 엔티티 조회
            Member findMember = em.find(Member.class, 1L);
            // 4. 다른 엔티티 매니저로 엔티티 조회
            Member findMemberByOtherManager = em2.find(Member.class, 1L);
        } catch (Exception ex) {
            tx.rollback();
        } finally {
            em.close();
            em2.close();
        }
        emf.close();
    }

1. 엔티티를 생성한 상태(비영속)

엔티티를 생성하여 아직 영속화하지 않았으므로, 비영속 상태이다.

2. 엔티티 영속

em.persist(member)로 엔티티를 영속화하여, 1차 캐시에 해당 엔티티를 저장한다.
트랜잭션이 커밋시 데이터베이스에 저장한다.

3. 엔티티 조회

1차 캐시에 조회하는 엔티티가 저장되어 있으므로, 1차 캐시에 있는 엔티티를 반환한다.
(데이터베이스를 조회하지 않는다)

em.find(Member.class, 1L) 메소드로 엔티티를 조회한다.
1번째 파라미터는 엔티티 클래스의 타입이고, 2번째 파라미터는 엔티티의 식별자 값이다.
em.find()를 호출하면 1차 캐시에서 엔티티를 찾고, 찾는 엔티티가 1차 캐시에 없으면 데이터베이스를 조회한다.

조회 시 흐름

  • member 엔티티 조회시 JPA는 영속성 컨텍스트의 1차 캐시에서 해당 PK값을 가진 엔티티를 찾는다.
  • 1차 캐시에 존재한다면 엔티티를 반환하고, 존재하지 않는다면 데이터베이스에서 조회한다.
  • 데이터베이스에서 조회 된 엔티티를 1차 캐시에 저장하고, 해당 엔티티를 반환한다.
  • 다음번에 member 엔티티를 조회하면, 데이터베이스를 거치치않고 1차 캐시에서 반환한다.

4. 다른 엔티티 매니저로 엔티티 조회

영속성 컨텍스트 영역이 다르므로, 1차 캐시에는 해당 엔티티가 없다.
따라서 데이터베이스를 조회하여 엔티티를 반환한다.


[ 영속 엔티티의 동일성 보장 ]

영속성 컨텍스트는 1차 캐시에 존재하는 id(식별자값)이 같은 엔티티에 대하여 동일성을 보장한다.

JPA는 1차 캐시를 통해 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다.

// 1. 식별자가 같은 엔티티 조회
Member findMember = em.find(Member.class, 1L);
Member findMember2 = em.find(Member.class, 1L);

// 2. 동일성 비교
System.out.println(findMember == findMember2);

1. 식별자가 같은 엔티티 조회

식별자가 같은 엔티티를 조회한다.

2. 동일성 비교

1차 캐시에 있는 같은 엔티티를 반환하므로, findMember == findMember2의 결과는 true로 반환된다.


[ 엔티티 등록시 쓰기 지연 ]

엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 모아둔다.

트랜잭션 커밋시 모아둔 쿼리를 데이터베이스에 보내는데 이것을 transactional write-behind이라 한다.

// 1. 엔티티 생성 (비영속 상태)
Member member = new Member(1L, "홍길동");
Member member2 = new Member(2L, "홍길동");

// 2. 트랜잭션 시작
tx.begin();

// 3. 엔티티 영속화
em.persist(member);
em.persist(member2);

// 4. 트랜잭션 커밋
tx.commit();

1. 엔티티 생성 (비영속 상태)

엔티티를 생성한다.
아직 영속화 되지 않았으므로 비영속화 상태이다.

2. 트랜잭션 시작

엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.

3. 엔티티 영속화

엔티티를 영속화한다.
아직 트랜잭션이 커밋되지 않았으므로, 내부 쿼리 저장소에 INSERT SQL을 쌓아둔다.

4. 트랜잭션 커밋

트랜잭션을 커밋하면 엔티티 매니저는 영속성 컨텍스트를 플러시한다.

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업인데, 이때 내부 쿼리 저장소에 쌓아둔 SQL를 데이터베이스에 보낸다.

영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화한 후에 실제 데이터베이스 트랜잭션을 커밋한다.

[ 엔티티 변경 감지(Dirty checking) ]

영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 변경된다면, 그 변경 된 내용을 감지하여 데이터베이스 커밋시점에 변경 된 내용을 데이터베이스에 자동으로 반영한다.

변경 감지 원리

JPA는 엔티티를 영속성 컨텍스트에 보관할 떄, 스냅샷(최초 상태)를 저장해둔다.
플러시 시점에 스냅샷과 엔티티를 비교해서 변경 된 엔티티를 찾는다.

tx.begin();
// 1. 엔티티 조회
Member findMember = em.find(Member.class, 1L);

// 2. 영속 엔티티 수정
findMember.changeUserName("둘리");

// 3. 트랜잭션 커밋
tx.commit();

1. 엔티티 조회

영속 엔티티를 조회한다.
조회 된 엔티티를 영속성 컨텍스트에 보관 할때, 엔티티의 스냅샷을 저장해둔다.

2. 영속 엔티티 데이터 수정

영속 엔티티 데이터를 수정한다.

3. 트랜잭션 커밋

1) 엔티티 매니저 내부에서 플러시가 호출된다.
2) 엔티티와 스냅샷을 비교하여 변경 된 엔티티를 찾는다.
3) 변경 된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
4) 쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.
5) 데이터베이스 트랜잭션을 커밋한다.

[ 참고 사이트 ]

자바 ORM 표준 JPA 프로그래밍
https://ttl-blog.tistory.com/108

profile
개발이 너무 좋아요

0개의 댓글