본문 바로가기

Back-End/JPA

JPA - 영속성 컨텍스트(1차 캐시, 동일성 보장, 쓰기 지연)

1차 캐시

엔티티를 영속성 컨텍스트에 저장할 때(em.persist()) 생성되는 메모리 안의 캐시이다. 엔티티는 먼저 1차 캐시에 저장되고 이후 같은 엔티티를 요청하면 DB에 요청하지 않고 1차 캐시에서 데이터를 반환하여 성능을 높인다.

1. 영속

  • 영속성 컨텍스트의 1차 캐시에 저장된다.
// 비영속
Tutor tutor = new Tutor(1L, "병천", 100);

// 영속, 1차 캐시에 저장
em.persist(tutor);

2. 영속된 Entity 조회

  • DB가 아닌 1차 캐시에 저장된 Entity를 먼저 조회한다.
public static void main(String[] args) {
    // EntityManagerFactory 생성
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
    // EntityManager 생성
    EntityManager em = emf.createEntityManager();
    // Transaction 생성
    EntityTransaction transaction = em.getTransaction();

    // 트랜잭션 시작
    transaction.begin();

    try {
        // 비영속
        Tutor tutor = new Tutor(1L, "wonuk", 100);

        // 영속
        System.out.println("persist 전");
        em.persist(tutor);
        System.out.println("persist 후");

        Tutor findTutor = em.find(Tutor.class, 1L);

        System.out.println("findTutor.getId() = " + findTutor.getId());
        System.out.println("findTutor.getName() = " + findTutor.getName());
        System.out.println("findTutor.getAge() = " + findTutor.getAge());

        // transaction이 commit되며 실제 SQL이 실행된다.
        transaction.commit();
    } catch (Exception e) {
        // 실패 -> 롤백
        e.printStackTrace();
        transaction.rollback();
    } finally {
        // 엔티티 매니저 연결 종료
        em.close();
    }
    emf.close();
}
  • 1차 캐시의 Entity를 조회한다.
    • 조회 SQL이 실행되지 않는다.
  • 트랜잭션 Commit 시점에 INSERT SQL이 실행된다.

3. 데이터베이스에 저장된 데이터를 조회할 때

  • 1차 캐시에 없으면 DB를 조회하고 그것을 1차 캐시에 저장하고 반환한다.
  • 1차 캐시는 동일한 트랜잭션 안에서만 사용이 가능하다.
  • 요청이 들어오고 트랜잭션이 종료되면 영속성 컨텍스트는 삭제된다.

동일성 보장

동일한 트랜잭션 안에서 특정 엔티티를 여러 번 조회해도 항상 같은 객체 인스턴스를 반환한다. 영속성 컨텍스트는 1차 캐시를 사용하여 같은 엔티티를 주복 조회해도 동일한 객체를 참조하게 하여 일관성을 유지한다.

  • 동일한 트랜잭션 내에서 조회된 Entity는 같은 인스턴스를 반환한다.
  • DB에 저장된 데이터를 조회하여 1차 캐시에 저장한다.
  • 1차 캐시에 저장된 데이터를 조회한다.
  •  
// DB 조회, 1차 캐시에 저장
Tutor findTutor1 = em.find(Tutor.class, 1L);
// 1차 캐시 조회
Tutor findTutor2 = em.find(Tutor.class, 1L);

System.out.println("findTutor1 == findTutor2 : " + findTutor1.equals(findTutor2));

// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
"
findTutor1 == findTutor2 : true
"

쓰기 지연

엔티티 객체의 변경 사항을 DB에 바로 반영하지 않고 트랜잭션이 커밋될 때 한 번에 반영하는 방식으로 이를 통해 성능을 최적화하고 트랜잭션 내에서의 불필요한 DB 쓰기 작업을 최소화 한다. 동일한 트랜잭션 내에서 생성된 SQL들을 Commit 시점에 한꺼번에 반영함.

  1. tutor1 1차 캐시에 저장
  2. 쓰기 지연 저장소에 tutor1 INSERT SQL 저장

  1. tuor2 1차 캐시에 저장
  2. 쓰기 지연 저장소에 tutor2 INSERT SQL 저장

  1. flush 되면서 SQL 쿼리 실행(쿼리만 만들어 놓고 DB 앞에서 대기)
  2. 트랜잭션이 Commit 되면서 실제 DB에 반영

  • 쓰기 지연이 발생하는 시점
    • flush() 동작이 발생하기 전까지 최적화한다.
    • flush() 동작으로 전송된 쿼리는 더이상 쿼리 최적화는 되지 않고, 이후 commit()으로 반영만 가능하다.
  • 쓰기 지연 효과
    • 여러개의 객체를 생성할 경우 모아서 한번에 쿼리를 전송한다.
    • 영속성 상태의 객체가 생성 및 수정이 여러번 일어나더라도 해당 트랜잭션 종료시 쿼리는 1번만 전송될 수 있다.
    • 영속성 상태에서 객체가 생성되었다 삭제되었다면 실제 DB에는 아무 동작이 전송되지 않을 수 있다.
    • 즉, 여러가지 동작이 많이 발생하더라도 쿼리는 트랜잭션당 최적화 되어 최소쿼리만 날라가게된다.
  • 키 생성전략이 generationType.IDENTITY 로 설정 되어있는 경우 생성쿼리는 쓰기지연이 발생하지 못한다.
    • why? 단일 쿼리로 수행함으로써 외부 트랜잭션에 의한 중복키 생성을 방지하여 단일키를 보장한다. 

'Back-End > JPA' 카테고리의 다른 글

JPA 연관관계 Mapping  (0) 2024.12.10
JPA - Entity 만들기  (0) 2024.12.10
JPA의 지연 저장소, 변경 감지, 그리고 flush와 Commit  (1) 2024.12.10
JPA - 영속성 컨텍스트(Entity)  (0) 2024.12.10
JPA에 대해서  (2) 2024.12.10