영속성 컨텍스트

이유진·2024년 8월 20일
0
post-thumbnail

🌷영속성 컨텍스트(Persistence Context)

영속성 컨텍스트란?

Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간

  • 영속성 컨텍스트의 기능

    • 1차 캐시

    • 쓰기 지연 저장소(ActionQueue)

    • 변경 감지(Dirty Checking)


👉 1차 캐시

데이터베이스로부터 조회한 엔티티 또는 처음으로 등록된 엔티티가 저장되는 곳.
주로 엔티티의 조회 성능을 향상시키기 위해 사용된다.

  • 영속성 컨텍스트는 내부적으로 캐시 저장소를 가지고 있다.
    • 우리가 저장하는 Entity 객체들이 1차 캐시 즉, 캐시 저장소에 저장된다고 생각하면 된다.
    • 캐시 저장소는 Map 자료구조 형태로 되어있다.
      • key에는 @Id로 매핑한 기본 키 즉, 식별자 값을 저장
      • value에는 해당 Entity 클래스의 객체를 저장
      • 영속성 컨텍스트는 캐시 저장소 Key에 저장한 식별자값을 사용하여 Entity 객체를 구분하고 관리한다.

< Entity 저장>

  • em.persist(memo); 메서드가 호출되면 memo Entity 객체를 캐시 저장소에 저장

    @Test
    @DisplayName("1차 캐시 : Entity 저장")
    void test1() {
        EntityTransaction et = em.getTransaction();
    
        et.begin();
    
        try {
    
            Memo memo = new Memo();
            memo.setId(1L);
            memo.setUsername("Robbie");
            memo.setContents("1차 캐시 Entity 저장");
    
            em.persist(memo);
    
            et.commit();
    
        } catch (Exception ex) {
            ex.printStackTrace();
            et.rollback();
        } finally {
            em.close();
        }
    
        emf.close();
    }

  • Debuging을 통해 key-value 형태로 정보가 저장 되어있음을 확인할 수 있다.

< Entity 조회>

  1. 캐시 저장소에 조회하는 Id가 존재하지 않은 경우

@Test
@DisplayName("Entity 조회 : 캐시 저장소에 해당하는 Id가 존재하지 않은 경우")
void test2() {
    try {

        Memo memo = em.find(Memo.class, 1);
        System.out.println("memo.getId() = " + memo.getId());
        System.out.println("memo.getUsername() = " + memo.getUsername());
        System.out.println("memo.getContents() = " + memo.getContents());


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

    emf.close();
}
  • DB에서 데이터를 조회만 하는 경우에는 데이터의 변경이 발생하는 것이 아니기 때문에 트랜잭션이 없어도 조회가 가능

  • Memo memo = em.find(Memo.class, 1); 호출 시 캐시 저장소에 해당 값이 존재하지 않기 때문에 DB에 SELECT 조회하여 캐시 저장소에 저장한 후 반환

  1. 캐시 저장소에 조회하는 Id가 존재하는 경우

  • em.find(Memo.class, 1);호출 시 캐시 저장소에 식별자 값이 1이면서 Memo Entity 타입인 값이 있는지 조회 → 값이 있다면 해당 Entity 객체를 반환
@Test
@DisplayName("Entity 조회 : 캐시 저장소에 해당하는 Id가 존재하는 경우")
void test3() {
    try {

        //캐시 저장소에 존재하지 않기 때문에 DB에 SELECT 조회하여 캐시 저장소에 저장
        Memo memo1 = em.find(Memo.class, 1);
        System.out.println("memo1 조회 후 캐시 저장소에 저장\n");

        //이미 캐시 저장소에 해당 값이 존재하기 때문에 DB에 조회하지 않고 캐시 저장소에서 해당 값을 반환
        Memo memo2 = em.find(Memo.class, 1); 
        System.out.println("memo2.getId() = " + memo2.getId());
        System.out.println("memo2.getUsername() = " + memo2.getUsername());
        System.out.println("memo2.getContents() = " + memo2.getContents());


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

    emf.close();
}
  • 1차 캐시 장점

    • DB 조회 횟수를 줄임

    • '1차 캐시'를 사용해 DB row 1개 당 객체 1개가 사용되는 것을 보장 (객체 동일성 보장)

< Entity 삭제>

@Test
@DisplayName("Entity 삭제")
void test5() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {

        // memo 객체를 캐시 저장소에 저장
        Memo memo = em.find(Memo.class, 2);

        // memo Entity 객체를 DELETED 상태로 만든다.
        em.remove(memo); 

        // commit하면 Delete SQL이 DB에 요청된다.
        et.commit();

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

    emf.close();
}

👉쓰기 지연 저장소(ActionQueue)

엔티티의 등록, 수정, 삭제 작업이 데이터베이스에 즉시 반영되지 않고, 트랜잭션이 끝날 때 한꺼번에 처리되도록 대기하는 저장소. 주로 성능 최적화를 위해 사용된다.

@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();
}
  • 쓰기 지연 저장소 확인

  • em > actionQueue를 확인해보면 insertions > executables에 Insert할 memo#2, memo#3 Entity 객체 2개가 들어가 있는 것을 확인할 수 있다.

  • 트랜잭션 commit 후

  • actionQueue에 있던 insertions 데이터가 사라진 것을 확인할 수 있다.

  • flush 메서드는 영속성 컨텍스트의 변경 내용들을 DB에 반영하는 역할을 수행
    • 즉, 쓰기 지연 저장소의 SQL들을 DB에 요청하는 역할을 수행
  • flush와 commit의 차이점!!
    • flush: 영속성 컨텍스트에 있는 변경 사항을 데이터베이스에 전송하지만, 트랜잭션이 커밋되지 않은 상태에서는 여전히 롤백할 수 있다. 즉, 실제로 데이터베이스에 저장되었다고 할 수는 없다.
    • commit: 트랜잭션을 종료하고, 변경 사항을 영구적으로 데이터베이스에 저장한다. commit이 호출되기 전에는 데이터베이스에 변경 사항이 최종적으로 반영되지 않는다.

👉변경 감지(Dirty Checking)

  • JPA는 어떻게 Update를 처리할까?
  • JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LoadedState)를 저장
    • 트랜잭션이 commit되고 em.flush(); 가 호출되면 Entity의 현재 상태와 저장한 최초 상태를 비교
    • 변경 내용이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장하고 모든 쓰기지연 저장소의 SQL을 DB에 요청
    • 마지막으로 DB의 트랜잭션이 commit 되면서 반영된다.
  • 따라서 변경하고 싶은 데이터가 있다면 먼저 데이터를 조회하고 해당 Entity 객체의 데이터를 변경하면 자동으로 Update SQL이 생성되고 DB에 반영된다.
    • 이러한 과정을 변경 감지, Dirty Checking이라 부른다.
@Test
@DisplayName("변경 감지 확인")
void test8() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {
        System.out.println("변경할 데이터를 조회합니다.");
        Memo memo = em.find(Memo.class, 4);
        System.out.println("memo.getId() = " + memo.getId());
        System.out.println("memo.getUsername() = " + memo.getUsername());
        System.out.println("memo.getContents() = " + memo.getContents());

        System.out.println("\n수정을 진행합니다.");
        memo.setUsername("Update");
        memo.setContents("변경 감지 확인");

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

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

    emf.close();
}

  • entityInstance는 Entity 객체의 현재 상태
  • entityEntry > loadedState는 조회했을 때 즉, 해당 Entity의 최초 상태
  • 트랜잭션 commit 후 em.flush(); 메서드가 호출되면 현재 상태와 최초 상태를 비교하고 변경이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장한 후 DB에 요청한다.
profile
🙌중요한건 꺾였는데도 그냥 하는 마음

0개의 댓글