[JPA] 영속성 컨텍스트 (Persistence Context)

3Beom's 개발 블로그·2022년 10월 4일
0

SpringJPA

목록 보기
1/21

출처

본 글은 인프런의 김영한님 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편 을 수강하며 기록한 필기 내용을 정리한 글입니다.

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


요약

  • 영속성 컨텍스트는 JPA 내부에서 DB와 소통하는 중간 단계이다.
  • 영속성 컨텍스트는 Entity Manager와 1:1 혹은 N:1 관계로 맺어진다.
  • 객체는 영속성 컨텍스트와 DB에 포함되어 있는지 여부에 따라 비영속, 영속, 준영속, 삭제 상태가 된다.
  • 영속성 컨텍스트를 통해 1차 캐시, 동일성, 쓰기 지연, 변경 감지, 지연 로딩 등의 이점을 가질 수 있다.
  • 영속성 컨텍스트의 변경 내용과 DB를 동기화 시키는 작업을 flush라 한다.

1. 영속성 컨텍스트와 엔티티 상태

1-1. EntityManagerFactory, EntityManager

  • 하나의 어플리케이션이 만들어 질 때, EntityManagerFactory가 생성된다.
  • EntityManagerFactory는 Client의 요청이 들어올 때마다 EntityManager를 생성하며, 요청이 끝나면 폐기한다.
  • EntityManager는 DB와 소통하며, 중간에 영속성 컨텍스트가 있다.
  • 영속성 컨텍스트는 EntityManager와 1:1 혹은 N:1 관계로 생성된다.

1-2. 영속성 컨텍스트

  • 영속성 컨텍스트는 EntityManager와 DB 사이의 중간 단계를 담당한다.
  • EntityManager의 persist(), find() 등의 함수들이 호출 될 때, 바로 DB에 반영되는 것이 아니라 영속성 컨텍스트에 반영되는 것이다.

1-3. 엔티티 상태

(1) 비영속 상태, 객체 생성

Cookie cookie = new Cookie();
cookie.setId(1L);
cookie.setTaste("Chocolate");
  • 다음과 같이 cookie라는 객체를 생성하고, 멤버 변수를 설정한다.
  • 이는 그냥 Cookie라는 클래스의 cookie 객체를 선언한 것이다.
  • 영속성 컨텍스트와 전혀 상관없다. : 비영속 상태

(2) 영속 상태, 1차 캐시, persist(), find()

<1차 캐시>

  • 영속성 컨텍스트 내에는 1차 캐시가 있다.
  • 객체가 1차 캐시에 들어가면 '영속 상태'가 되며, 이는 'flush' 과정 전까지 DB에 반영되지 않는다.
  • 1차 캐시에 들어가는 과정은 다음 두가지를 통해 이루어 질 수 있다.
    -> persist(), find()

<persist()>

eM.persist(cookie);
  • persist() 함수를 호출하면 cookie 객체는 영속성 컨텍스트에 반영된다.
  • 이는 영속성 컨텍스트에 존재하는 1차 캐시에 cookie 내용을 넣어두는 것이다.
  • 해당 과정을 통해 영속성 컨텍스트의 관리 하에 들어간다. : 영속 상태
  • 아직 DB에 반영되진 않는다. : 쿼리를 전달하지 않는다.

<find()>

Cookie chocoCookie = eM.find(Cookie.class, 1L);
  • find() 함수를 통해서도 객체가 1차 캐시에 들어갈 수 있다.
  • find() 함수는 아래 과정을 통해 수행된다.
  1. find() 함수의 파라미터로 전달된 내용을 1차 캐시에서 먼저 조회한다.
  2. 1차 캐시에 해당 객체가 없을 경우, DB에 쿼리문을 전달하여 가져온다.
  3. 가져온 객체를 먼저 1차 캐시에 저장한 후, 반환한다.
  4. 다음 동일한 객체를 find() 함수로 조회할 경우, DB에서 가져오지 않고 1차 캐시에서 가져올 수 있다.
    -> 불필요한 DB 소통을 줄일 수 있다.

(3) 준영속, 삭제 상태, detach(), remove()

eM.persist(cookie); // 영속 상태
eM.detach(cookie); // 준영속 상태
  • 준영속 상태는 영속성 컨텍스트 내 1차 캐시에 있는 객체를 다시 뺌으로써 영속성 컨텍스트의 관리를 받지 않는다.
eM.remove(cookie);
  • 삭제 상태는 1차 캐시 및 DB에서 아예 삭제된 상태이다.

2. 영속성 컨텍스트의 장점

  • 영속성 컨텍스트의 장점은 모두 하나의 트랜잭션 내에서 가능하다는 것을 유의해야 한다.

2-1. 1차 캐시

  • persist() 혹은 find() 함수를 통해 1차 캐시에 등록될 수 있다.
  • 1차 캐시를 통한 이점은 find(), 쓰기 지연, Dirty Checking에서 나타난다.

(1) find()

Cookie chocoCookie1 = eM.find(Cookie.class, 1L);
Cookie chocoCookie2 = eM.find(Cookie.class, 1L);
  • 다음과 같이 동일한 데이터를 여러번 find 할 경우, 첫번째 find() 과정에서 DB로부터 가져온 후, 1차 캐시에 저장하게 된다.
  • 이후에 수행되는 find() 과정은 DB와의 소통 없이 1차 캐시에서 가져올 수 있게 된다.

(2) 쓰기 지연 (transactional write-behind)

eM.persist(chocoCookie1);
eM.persist(chocoCookie2);
...
eM.persist(chocoCookie10);
  • 영속성 컨텍스트는 'flush' 과정 전까지 DB로 쿼리문을 전달하지 않는다.
  • 따라서 다음과 같이 여러번의 persist() 과정이 있어도 이를 1차 캐시에 저장해 둔다.
  • 이와 더불어 각각의 insert 쿼리문을 SQL 저장소에 쌓아둔다.
  • 이후 'flush' 과정을 통해 한번에 DB에 반영시킨다.
  • 이는 구현 내용에 따라 버퍼링 기능을 기대할 수 있다.

(3) Dirty Checking

  • 다음은 find() 를 통해 조회한 데이터를 수정하는 과정이다.
Cookie chocoCookie = eM.find(Cookie.class, 1L);
cookie.setTaste("vanilla");

// 필요할 것 같지만 필요하지 않다. 오히려 쓰면 안된다.
// eM.persist(cookie);

tX.commit();
  • 일반적으로 DB의 데이터를 수정할 때, 다음과 같은 과정을 생각하게 된다.
    1. DB로부터 데이터를 가져온다.
    2. 가져온 데이터의 내용을 수정한다.
    3. 다시 DB로 보내 내용을 update 한다.
  • 하지만 JPA에서는 3번 과정을 생략한다. : Dirty Checking
  • Dirty Checking 과정은 아래와 같이 이루어 진다.
    1. find() 함수를 통해 조회한 데이터가 1차 캐시에 저장된다. 이때, 원본을 따로 또 저장한다.
    2. 데이터 내용을 수정한다.
    3. commit()을 통해 flush가 수행될 때, 원본과 비교한다.
    4. 수정된 내용이 있을 경우, UPDATE 쿼리를 SQL 저장소에 추가한 후, DB로 전달한다.
  • 따라서 따로 DB에 반영하겠다는 코드를 작성하지 않아도 알아서 다 해준다.
  • 이는 Java의 컬렉션에 저장되어 있는 값을 수정하듯이 엔티티를 활용하는 것에 의미가 있다.


  • 하지만 만약 수정한 객체가 DB에 반영되지 않기를 원할 경우, '준영속 상태'를 활용하면 된다.

<준영속 상태 활용>

Cookie chocoCookie = eM.find(Cookie.class, 1L);
chocoCookie.setTaste("vanilla");

eM.detach(chocoCookie);

tX.commit();
  • 다음과 같이 detach() 함수를 통해 chocoCookie 객체를 1차 캐시에서 빼낼 경우(준영속 상태), 이는 flush 과정에서 제외된다.
  • 준영속 상태로 만드는 방법은 아래와 같다.
    -> eM.detach(entity) : 특정 객체를 골라서 빼낼 수 있다.
    -> eM.clear() : 영속 컨텍스트 내 모든 객체를 뺀다.
    -> eM.close() : 영속 컨텍스트를 아예 종료시킨다.

2-2. 동일성

// 동일한 객체를 서로 다른 객체에 저장한다.
Cookie chocoCookie1 = eM.find(Cookie.class, 1L);
Cookie chocoCookie2 = eM.find(Cookie.class, 1L);

// true 출력
System.out.println(chocoCookie1 == chocoCookie2);
  • 다음과 같이 1차 캐시에 저장되어 있는 동일한 객체를 가져와서 이를 비교할 경우, 동일함(==)을 보장해준다.
  • 이 또한 마치 Java의 컬렉션 내에서 동일한 값을 꺼내와 비교한 것과 같은 느낌을 받을 수 있다.

3. flush

  • flush는 영속성 컨텍스트의 변경 내용을 DB에 반영하는 과정이다.
    (영속성 컨텍스트 내용과 DB 내용을 동기화 시켜주는 작업이라 생각하면 된다.)

3-1. flush 과정

  • 기존 내용에서 수정된 내용이 있는지 확인 후, 있을 경우 해당 내용을 담은 쿼리문을 SQL 저장소에 등록 (Dirty Checking)
  • SQL 저장소의 쿼리를 DB에 전송.
    (flush 과정이 수행된다고 1차 캐시 내용이 모두 지워지거나 아예 마무리 지어지는건 아니다. DB 내용을 동기화 시켜주는 것 뿐.)
    (하지만 주로 commit()을 통해 flush 과정이 이루어 진다.)

3-2. flush 과정이 수행되는 경우

(1) 트랜잭션이 커밋될 때

  • 다음과 같이 commit() 함수가 호출되면 flush 과정이 자동으로 수행된다.
eM.persist(chocoCookie);
tX.commit();

(2) 직접 호출 : flush()

  • 다음과 같이 commit() 전, DB에 바로 적용하는 등 필요에 따라 직접 호출도 가능하다.
eM.persist(chocoCookie);
eM.flush();
...
tX.commit();

(3) JPQL 쿼리 실행

  • 다음과 같이 JPQL이 활용될 경우, 자동으로 flush 과정이 수행된다.
eM.persist(chocoCookie);

jpql = eM.createQuery("select c from Cookie c", Cookie.class);
  • 위 코드의 경우, JPQL로 인한 flush 때문에 'chocoCookie' 객체의 persist() 과정이 commit() 이전에 바로 DB에 반영되게 된다.
  • 만약 이를 방지하고 싶다면 flush mode를 바꿔주면 된다.

< flush mode option >

  • 활용 : eM.setFlushMode(FlushModeType.COMMIT)
  • 종류
    -> FlushModeType.AUTO : Default.
    -> FlushModeType.COMMIT : commit()에서만 자동으로 flush.
profile
경험과 기록으로 성장하기

0개의 댓글