Spring/JPA

[JPA] 영속성 컨텍스트

SeongHun._. 2022. 12. 27. 05:40

EntityManagerFactory & EnitityManger

영속성 컨텍스트에 대해 공부하기 전에 EntityManagerFactory와 EnitityManger를 알아야 한다.

EntityManagerFactory는 생성되는 시점에 DB 커넥션 풀을 생성한 후, 클라이언트의 요청이 들어올 때 마다 EnitityManger를 생성한다.

EnitityManger는 DB 연결이 필요한 시점(보통 트랜잭션이 시작되는 경우)에 커넥션 풀에 있는 Connection을 얻는다.

 

 

EntityManagerFactory가 EntityManager를 사용하는 이유

EntityManagerFactory는 생성되는 시점에 DB 커넥션 풀을 생성하기 때문에 생성 비용이 매우 큰 편이다.

반면에 EntityManager의 생성 비용의 별로 들지 않기 때문에 EntityManagerFactory는 필요에 따라 EntityManager를 생성하여 사용한다.

 

EntityManagerFactory는 스레드 세이프(Thread Safe)하다.

여러 스레드가 동시에 접근하여도 안전한 반면에 EntityManager는 여러 스레드가 동시에 접근하게 될 경우 동시성 문제가 발생하게 된다. 즉 EntityManager는 절대로 공유되서는 안된다.

 

동시성 문제란?
동일한 자원에 여러 스레드가 동시에 같은 인스턴스의 필드 값을 변경하면서 발생하는 문제를 의미한다.
 

동시성 이슈(Concurrency Issue)

Concurrency 주말에(2022-03-19. Sat) [OKKY - 스프링 쓰레드 ...] 에 관한 질문에 답변을 하면서, Spring 을 사용하면서 꼭 알아야할 동시성 이슈에 대해서 공유하고자 합니다. 동시성 이슈란? 동시성 이슈란

techvu.dev

 

 

영속성 컨텍스트(Persistence Context)

엔티티(Entity)를 저장하고 관리하는 환경을 의미한다.

애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할을 한다고 볼 수 있다. EntityManager를 통해 엔티티(Entity)를 저장하거나, 조회하면 EntityManager는 영속성 컨텍스트에 엔티티(Entity)를 보관하고 관리한다. 이때 관리되는 엔티티(Entity)를 영속 상태라고 한다.

 

 

순수 JPA로 영속성 컨텍스트를 실행하기 위해 다음과 같은 작업을 해야한다.

 

EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

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

 

  1. Persistence를 통해 EntityManagerFactory 생성 후 EntityManagerFactory로 EntityManager를 생성할 수 있다. 
  2. 영속성 컨텍스트는 트랜잭션 범위 내에만 사용할 수 있기 때문에 EntityManager를 통해 Transcation을 얻어 실행(begin)시킨다.
  3. 트랜잭션 범위 내 원하는 엔티티를 조회, 저장 등등 을 진행하고 트랜잭션 범위가 끝나는 시점에 플러시를 통해 DB에 데이터를 반영해준다.
  4. 만약 예외처리가 될 경우 try문 내에 실행된 코드들을 전부 롤백시켜 DB을 보호한다.
  5. 마지막으로 EntityManager → EntityManagerFactory 순으로 종료시켜준다.

아직 트랜잭션에 대해 설명하지 않았기 때문에 영속성 컨텍스트가 실행되는 범위라고만 생각하면 될 것 같다.

이제부터 아래에 보이는 코드들은 모두 트랜잭션 범위 내에 구현된 것으로 위 내용을 생략하여 코드를 설명하려고 한다.

 

 

영속성 컨텍스트의 기능

1차 캐시

1차 캐시는 EntityManager가 관리하는 영속성 컨텍스트 내부에 있는 첫 번째 캐시를 의미한다.

1차 캐시의 생명 주기는 트랜잭션 범위 내에만 유지되며, 범위를 벗어나면 1차 캐시는 삭제된다.

 

Member member = new Member();
member.setUsername("user1");
// 엔티티 영속화
em.perist(member);

 

위 코드를 실행하면 em.persist(member)를 통해 member가 영속성 컨텍스트(1차 캐시)에 저장된다.

 

이후 member를 조회 해보자.

 

Member member = new Member();
member.setUsername("user1");
// 엔티티 영속화
em.perist(member);
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, member.getId());

 

DB에서 데이터를 조회하기 위해서는 DB에 연결해서, DB에 존재하는 Member 테이블의 값들 중 PK가 member.getId()와 같은 값을 찾아오는 SQL을 수행해야 한다. 그러나 JPA를 사용하면 1차 캐시 덕분에 DB에 바로 접근하지 않고도 member의 값을 가져올 수 있다.

 

 

조회 과정

  1. 조회 시 처음 1차 캐시에 해당 엔티티가 있는지 탐색한다. → 엔티티가 있다면 해당 객체를 리턴
  2. 조회 결과 1차 캐시에 엔티티가 없다면 DB에 접근해서 값을 탐색한다.
  3. 탐색 결과를 엔티티를 바로 반환하는 것이 아닌 다음 탐색에서 재사용할 수 있도록 1차 캐시에 저장한다.

 

참고

  • 1차 캐시는 서로 공유하지 않는다.
  • 10명의 클라이언트가 요청을 보내면 EntityManager도 10개가 생성되며, 1차 캐시도 10개 생긴다.
  • EntityManager는 보통 DB 트랜잭션 단위로 생성하고, 트랜잭션이 끝나는 시점에 종료된다.
  • EntityManager가 종료되면 EntityManager의 1차 캐시에 존재하는 데이터들도 모두 삭제된다.
  • 트랜잭션의 유지 범위는 매우 짧은 편이기 때문에 1차 캐시를 통해 큰 성능 이점을 얻기는 어렵다.

 

영속 엔티티의 동일성(Identity) 보장

Member findMember1 = em.find(Member.class, 1L);
Member findMember2 = em.find(Member.class, 1L);

// true 반환
System.out.println(findMember1 == findMember2);

 

JPA는 하나의 트랜잭션 범위 내에 존재하는 id(식별자 값)가 같은 엔티티에 대해 동일성을 보장해준다.

여기서 동일성은 값 뿐만이 아니라 실제 인스턴스 자체가 같다는 의미를 가진다.

 

 

트랜잭션을 지원하는 쓰기 지연

트랜잭션 범위 내 엔티티 값을 변경한다고 바로 DB에 접속하여 데이터를 변경하지 않는다.

트랜잭션 내부에 영속 상태의 엔티티의 값을 변경하면 SQL 쿼리는 쓰기 지연 저장소에 쿼리문들을 생성하여 쌓아둔다.

이후 트랜잭션 범위가 끝나는 시점인 commit이 되거나, 직접 강제로 flush()를 이용하는 경우 DB에 접속하여 쿼리를 보낸다.

여기서 플러시(flush)란?

flush는 영속성 컨텍스트의 변경 내용을 쿼리를 통해 DB에 반영한다.(이때 1차 캐시는 사라지지 않는다.)

1차 캐시를 지우지 않고 쿼리를 DB에 날려 DB와 싱크를 맞추는 역할을 한다.

 

 

변경 감지(Dirty Checking)

엔티티의 수정이 일어나는 경우 개발자가 직접 영속성 컨텍스트에 따로 알려주지 않아도 알아서 변경 사항을 체크하고 DB에 접속하여 값을 변경시켜준다. 이를 변경 감지라고 부른다.

 

1차 캐시에는 @id와 Entity 말고도 "스냅샷"이라는 컬럼이 존재한다.

엔티티가 1차 캐시에 저장될 때, 저장되는 시점의 상태를 스냅샷으로 만들어 1차 캐시에 보관한다.

트랜잭션이 commit이 되는 시점에 현재 엔티티와 스냅샷을 비교하고, 이때 일치하지 않는 부분이 있다면 이를 감지하여 DB에 반영해준다.

 

여기서 중요한 것은 commit이 되는 시점에서 현재 엔티티와 스냅샷을 비교한다는 것이다.

이말은 즉, commit이 되기 전에 엔티티를 수정했다가, commit이 일어나는 시점 전에 엔티티가 원상 복구가되면 값이 값이 변경되지 않는다.

 

변경감지 과정

  1. 트랜잭션을 commit하면 EntityManager의 내부에서 플러시가 호출된다.
  2. 엔티티와 스냅샷을 비교하여 변경된 엔티티를 감지한다.
  3. 변경된 엔티티가 있다면 수정 쿼리를 쓰기 지연 SQL 저장소에 생성해둔다.
  4. 트랜잭션 범위가 끝나는 시점에 쓰기 지연 SQL 저장소에 담아둔 쿼리를 DB에 반영한다.

 

Reference

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

1. 엔티티 (Entity) 와 엔티티 매니저 (Entity Manager)

엔티티 매니저 특정 작업을 위해 데이터베이스에 액세스하는 역할을 담당한다. 엔티티를 DB 에 등록/수정/삭제/조회(CRUD) 하는 역할이며, 엔티티와 관련된 일을 처리하는 엔티티 관리자이다. - 엔

lhwn.tistory.com