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

최권민·2023년 3월 12일
0

CS스터디

목록 보기
1/8

✨ 스프링의 영속성(Persist)이란 무엇인가?


스프링을 배우다 보면, 영속성(Persist)이라는 말을 자주 사용한다. 대표적으로 영속성 컨텍스트(Persistence Context)라는 것으로 사용을 하는데, 누구든 알아들을 수 있도록

  1. 영속성이 무엇인지,
  2. 엔티티가 무엇인지,
  3. 영속성 컨텍스트는 무엇이며 왜 사용하는지

의 순서로 진행하고자 한다.

1. 영속성이란?


  • 위키피디아에서는 영속성을 이렇게 정의한다.

  • 영속성(persistence)은 데이터를 생성한 프로그램의 실행이 종료되더라도 사라지지 않는 데이터의 특성을 의미한다. 영속성은 파일 시스템, 관계형 테이터베이스 혹은 객체 데이터베이스 등을 활용하여 구현한다. 영속성을 갖지 않는 데이터는 단지 메모리에서만 존재하기 때문에 프로그램을 종료하면 모두 잃어버리게 된다. 결국 영속성은 특정 데이터 구조를 이전 상태로 복원할 수 있게 해주어 프로그램의 종료와 재개를 자유롭게 해준다.

  • 정리하자면 어떠한 데이터의 상태를 유지시켜 주는 것이라고 할 수 있다.

  • 대표적으로 영속성 컨텍스트에서 사용하는데, 그림으로 나타내면 아래와 같다.

  • 우리가 알아보고자 하는 영속성 컨텍스트(Persistence Context)는 결국 Entity에 변화된 무언가를 영구적으로 저장하는 것이라고 볼 수 있다.

2. 엔티티란?


  • DB를 공부했지만, Entity라는 용어는 그냥 스쳐가듯 지나간 경우가 많아서, 공부하기 전까지는 Entity를 'Spring 에서만 쓰는 것' 정도로 넘어가곤 했다.

  • 일단 엔티티의 개념을 먼저 말하고 간다면, 엔티티는 '개체화되기 이전의 개념적 개체'를 뜻한다.

(https://stackoverflow.com/questions/26820023/difference-between-entity-and-record#:~:text=%22A%20collection%20of%20fields%20that,database%20is%20an%20%22entity%22)

  • 엔티티에서 정의된 요건들로 개체들을 만들어 테이블에 저장한다면, 실제적인 테이블이 되지만 그 이전의 개념적인 조건들을 정의한 상태를 엔티티라고 볼 수 있다.

  • 즉 논리모델 = Entity, 물리모델 = Table 이다.

  • DB를 배우면서 공부했던 개념 단계를 생각해보면 된다.

  • 엔티티의 특징을 간단하게 언급하고 간다면,
    • 식별자 - 유일한 식별자를 갖고 있어야 한다. ex) 주민번호, ID 등...
    • 인스턴스 집합 - 2개 이상의 인스턴스가 있어야 한다.
    • 속성 - 반드시 속성을 가지고 있어야 한다. ex) 학생에 학번, 이름, 주소 등...
    • 관계 - 다른 엔티티와 최소 한 개 이상 관계가 있어야 한다. ex) 학생은 이름을 갖고 있음.
    • 업무 - 업무에서 관리되어야 하는 집합이다. ex) 학생, 성적
  • 라고 한다. 스프링에서 Entity를 만들면서 어느정도는 적용하고 있던 특징이라고 생각한다.

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


  • 위에서 영속성의 개념을 파악하고 내려왔지만, 영속성 컨텍스트를 정의하자면 엔티티를 영구 저장하는 환경 이라는 뜻이다.

  • 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할을 한다.

  • Spring에서는, EntityManager에 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

    • 보통 EntityManager를 em이라고 줄여서 사용하기에 em이라는 약어가 더 와닿을 수도 있다.

  • 위와 같이 다수의 EntityManager를 하나의 영속성 컨텍스트가 관리한다.
em.persist(entity);

위와 같은 코드는 실제로 DB에 저장한다는 것이 아니라 영속성 컨텍스트를 통해서 Entity를 영속화시키겠다는 의미이다.

  • 아래는 영속화가 필요한 이유인데, 정리를 잘 해 주셨다고 생각해서 들고왔다.
EntityManager가 생성되면 1:1로 영속성 컨텍스트가 생성된다.

하지만 컨테이너 환경의 JPA에서는 여러 EntityManager가 하나의 영속성 컨텍스트를 공유하게 된다.

 

컨테이너 환경의 JPA라 함은 무엇일까?

컨테이너를 사용하는 환경 (ex. spring)에서는 개발자가 EntityManager를 직접 생성하지 않고 컨테이너에 위임한다.

 

일반적으로 스프링은 싱글톤 기반으로 동작하기 때문에 속성값은 모든 thread가 공유하게 된다.

그래서 여러 thread가 동시에 접근하면 동시성 문제가 발생할 수도 있다.

그렇다면 스프링이 관리하는 EntityManager의 Thread-safe를 어떻게 보장할까?

EntityManager를 Proxy를 통해서 감싸고 EntityManager 메소드 호출 때 마다 Proxy를 통해서 EntityManager를 생성한다.

EntityManager를 직접 사용하는 경우에는 @PersistenceContext를 사용하면 된다.

(출처 : https://jaeho214.tistory.com/73 )

  • 내용의 핵심은 '동시성 문제'의 회피라고 할 수 있겠다.
  • 스프링 컨텍스트는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다.

    • 트랜잭션이란? 질의어(SQL)를 이용하여 데이터베이스에 접근하는 작업 단위
  • 트랜잭션이 시작될 때 영속성 컨텍스트를 생성하고 트랜잭션이 끝날 때 영속성 컨텍스트를 끝낸다.

  • 즉, 트랜잭션이 같으면 같은 영속성 컨텍스트를 사용하는 것이다.

  • 여러 EntityManager를 사용해도 한 트랜잭션으로 묶이면 영속성 컨텍스트를 공유한다.

  • 같은 EntityManager를 사용해도 트랜잭션에 따라 접근하는 영속성 컨텍스트가 다르다.
  • 따라서 같은 EntityManager를 호출해도 접근하는 영속성 컨텍스트가 다르므로 멀티스레드에서 안전하다.

🎈 Entity의 생명 주기

Entity의 생명 주기는 크게 비영속, 영속, 준영속, 삭제 네 가지로 나뉜다.

  • 비영속 (new / transient)

    • 영속성 컨텍스트와는 전혀 관계가 없는 상태로 객체를 생성만 한 상태를 의미한다.
    Member member = new Member();
  • 영속 (managed)

    • 영속성 컨텍스트에 저장된 상태로 Entity가 영속성 컨텍스트에 의해 관리되고 있는 상태를 의미한다.
    em.persist(member);
  • 준영속 (detached)

    • 영속성 컨텍스트에 저장되었다가 분리된 상태로 영속성 컨텍스트에서 지운 상태를 말한다.
    // 엔티티를 영속성 컨텍스트에서 분리해 준영속 상태로 만든다.
    em.detach(member);
    // 영속성 컨텍스트를 비워도 관리되던 엔티티는 준영속 상태가 된다.
    em.clear();
    // 영속성 컨텍스트를 종료해도 관리되던 엔티티는 준영속 상태가 된다.
    em.close();
    • 준영속 상태의 특징
      • 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
      • 식별자 값을 가지고 있다.
  • 삭제 (removed)

    • 엔티티를 영속성 컨텍스트와 실제 데이터베이스에서 삭제한다.
    em.remove(member);

🎈 영속성 컨텍스트의 특징

  • 영속성 컨텍스트의 식별자 값

    • 영속성 컨텍스트는 엔티티를 식별자 값으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야 한다.
  • 영속성 컨텍스트와 데이터베이스 저장

    • JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하는데 이를 flush라고 한다.
  • 영속성 컨텍스트가 엔티티를 관리하면 다음과 같은 장점이 있다.
  1. 1차 캐시

    • 영속성 컨텍스트 내부에는 캐시가 있는데 이를 1차 캐시라고 한다. 영속 상태의 엔티티를 이곳에 저장한다.(persist 상태)
    • 1차 캐시의 키는 식별자 값(데이터베이스의 기본 키)이고 값은 엔티티 인스턴스이다. 조회하는 방법은 다음과 같다.
    // em.find(엔티티 클래스 타입, 식별자 값);
    Member member = em.find(Member.class, "member1");
    • 조회의 흐름

      1. 1차 캐시에서 엔티티를 찾는다.
      2. 엔티티가 존재할 경우 메모리에 있는 1차 캐시에서 엔티티를 조회한다.
      3. 없으면 데이터베이스에서 조회한다.
      4. 조회한 데이터로 엔티티를 생성해 1차 캐시에 저장한다. (엔티티를 영속 상태로 만든다.)
      5. 조회한 엔티티를 반환한다.
  1. 영속 엔티티의 동일성 보장

    • 영속성 컨텍스트는 엔티티의 동일성을 보장한다.
    Member a = em.find(Member.class, "member1");
    Member b = em.find(Member.class, "member1");
    System.out.print(a==b) // true
  2. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

    • em.find(member) 를 사용해 member를 저장해도 바로 INSERT SQL이 DB에 보내지는 것이 아니다. (중요!!)
    • 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 INSERT SQL을 모아둔다.
    • 트랜잭션을 커밋할 때 모아둔 쿼리를 DB에 전송한다. (성능적으로 좋다.)
    • 이것을 트랜잭션을 지원하는 쓰기 지연이라고 한다.
  3. 변경 감지

    • JPA로 Entity를 수정할 때는 단순히 엔티티를 조회하여 데이터를 변경하면 된다.
    1. 트랜잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시가 호출된다.
    2. 엔티티와 스냅샷을 비교하여 변경된 엔티티를 찾는다.
    3. 변경된 엔티티가 있으면 수정 쿼리를 생성하여 쓰기 지연 SQL 저장소에 저장한다.
    4. 쓰기 지연 저장소의 SQL을 플러시한다.
    5. 데이터베이스 트랜잭션을 커밋한다.
    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    transaction.begin();
    
    Member member = em.find(Member.class, "member1");
    member.setName("최권민");
    
    transaction.commit();

🚿 플러시

  • 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
  • 영속성 컨텍스트의 엔티티를 지우는 게 아니라 변경 내용을 데이터베이스에 동기화하는 것이다.
  • 플러시의 흐름
    1. 변경 감지가 동작하여 스냅샷과 비교해서 수정된 엔티티를 찾는다.
    2. 수정된 엔티티는 수정 쿼리를 만들어서 SQL 저장소에 등록한다.
    3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.
  • 플러시 하는 방법
    1. em.flush()
    2. 트랜잭션 커밋 시 자동 호출
    3. JPQL 쿼리 실행 시 자동 호출

🚩 출처


참고한 모든 자료는 출처에 추가하고 있습니다. 만약 추가되지 않은 정보가 있다면, 말씀해 주시면 추가하도록 하겠습니다.

profile
하나의 궤적을 따라서

0개의 댓글