[Spring JPA] 영속성 컨텍스트의 기능

2024. 3. 7. 18:43BE/Spring

728x90

본 포스팅은 영속성 컨텍스트의 4가지 기능을 다룹니다.

영속성 컨텍스트란? 

  • 영속성 컨텍스트는 Spring JPA의 Entity 객체를 관리하기 위해 만들어진 공간
  • 영속성 컨텍스트에 저장하기 위해 Entity Manager 객체가 persist 메소드를 호출해야함.
    • Entity Manager는 @PersistenceContext를 통해 의존성 주입을 받을 수 있음
  • 영속성 컨텍스트에 저장한 후, commit메소드를 호출해야 db에 반영이됨.

트랜잭션이란?

  • 트랜잭션은 DB 데이터들의 무결성과 정합성을 유지하기 위한 하나의 논리적 개념
  • 여러 개의 쿼리들이 하나의 트랜잭션에 포함될 수 있고, 여러 개의 쿼리들을 모아 한 번에 db에 반영함.
  • 모든 쿼리들이 성공적으로 수행되면 영구적으로 db에 변경을 반영하지만, 하나라도 실패하면 모든 변경 사항을 되돌림.

1차 캐시

  • entity manager가 persist 메소드를 호출하면
    • entity 객체가 1차 캐시에 저장됨.
    • 비영속(New/Transient) 상태인 Entity 객체를 영속(Managed)상태로 만듬
    • 쓰기지연 SQL 저장소에 INSERT 쿼리를 생성함
  • 조회를 할 때, 1차 캐시에 있으면 1차 캐시에 있는 정보를 통해 조회를 진행하고, 1차 캐시에 없으면 db에 요청을 보내 조회함.

장점

  • 1차 캐시를 사용하면 db에 요청 보내는 횟수가 줄어들어 속도가 향상될 수 있음.

쓰기 지연 SQL 저장소

  • entity manager가 persist 메소드를 호출하면 SQL 쿼리들이 쓰기 지연 저장소에 저장됨.
  • commit 메소드가 호출되면, flush가 되면서 쓰기 지연 저장소에 있는 모든 SQL 쿼리들이 db에 반영됨.
@Test
@DisplayName("쓰기 지연 저장소 확인")
void test6() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {
        Memo memo = new Memo();
        memo.setId(2L);
        memo.setUsername("Robbert");
        memo.setContents("쓰기 지연 저장소");
        em.persist(memo);

        Memo memo2 = new Memo();
        memo2.setId(3L);
        memo2.setUsername("Bob");
        memo2.setContents("과연 저장을 잘 하고 있을까?");
        em.persist(memo2);

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

변경 감지(Dirty Checking)

  • entity manager의 commit 메소드가 호출되면, 내부적으로 flush 메소드가 먼저 호출됨
  • flush 메소드가 호출되면, 1차 캐시에 저장된 엔티티와 엔티티의 최소 상태인 스냅샷(LoadedState)을 비교함
  • 만약 변경 사항이 있다면, 업데이트 sql 쿼리를 쓰기 지연 sql 저장소에 저장함
  • 쓰기 지연 sql 저장소에 있는 sql 쿼리들이 flush 되면서 db에 반영됨

동일성 보장

  • 영속성 컨텍스트는 1차캐시를 이용해 동일한 @Id 를 가지는 Entity 에 대한 동일성 보장을 해준다.
  • Entity 를 조회한다는 것은 아래와 같은 흐름을 거치게된다.
    1. (@Id 를 이용한 조회라면) 1차캐시에 @Id 에 해당하는 Entity 가 있는지 먼저 조회한다.
      1. 만약 1차 캐시에 @Id에 해당하는 Entity가 있다면, 1차 캐시에 있는 Entity를 반환함
      2. 없으면 아래의 절차 진행
    2. 데이터베이스에 SELECT 쿼리를 실행해 Entity 정보를 조회한다.
    3. 조회한 Entity 를 영속(Managed) 상태로 만든다. → 이때 1차캐시에도 저장된다!!
    4. 1차캐시에 저장된 Entity 를 반환한다.
  • 즉, 조회 결과로 나온 Entity 는 항상 영속 상태이며 1차캐시에 저장되어 관리되는 Entity 다.
  • 그리고 1차캐시에는 @Id 하나당 하나의 Entity 를 저장해두고 있기 때문에 여러번 조회하더라도 @Id 가 같다면 해당 Entity 들은 모두 동일한 주소를 가지는 인스턴스가 된다!! (단, 모든 Entity 가 영속 상태라는 전제가 필요하다!!)

헷갈리는 것

  • commit 메소드, flush 메소드, flush는 어떻게 다른 것일까 ? 
  • commit 메소드가 호출되면, flush 메소드가 호출됨.
  • flush 메소드가 호출되면 변경 사항에 대해 업데이트 쿼리를 쓰기 지연 저장소에 저장한 후 db에 반영이 되는데, 이러한 과정 자체를 flush 작업이 수행된다고 지칭함.
    • 즉, commit 메소드가 호출되면 flush 작업이 수행되는데, 이를 flush 라는 이름의 메소드를 호출하여 진행하는 것

추가 정리

  • flush 메소드는 영속성 컨텍스트를 비우지는 않고, 영속성 컨텍스트의 변경 내용을 데이터베이스에 flush하게됨.
  • 만약 flush 메소드를 호출한 후, commit 메소드를 호출하면 쿼리가 날라가지 않게됨
  • 영속성 컨텍스트와 트랙잭션의 생명주기는 동일하다.