Post

03 영속성 컨텍스트

03 영속성 컨텍스트

1. Note

  • 어렵다 어렵다 어렵다!!!
  • 필요한 데이터를 최소한의 쿼리로 조회해서, 영속성 컨텍스트에 캐싱하고,
  • 한번에 몰려서 플러쉬가 날라가지 않게 조절하는것이 JPA를 활용하는 방법인건가

2. 영속성 컨텍스트

1. 영속성 컨텍스트

내 그림

  • 엔티티(Entity)를 영구 저장하는 환경
  • 실제로는 애플리케이션과 데이터베이스 사이에서 엔티티를 담아두고 관리하는 논리적인 공간(메모리 캐시)
  • EntityManager를 통해 이 공간에 접근하고 관리

2. JPA의 영속성

  • 영속 상태만 JPA가 관리
  • 영속 상태에서만 변경 감지가 동작
  • 트랜잭션이 끝나면 대부분 준영속 상태
  • 준영속 상태에서는 값을 바꿔도 DB에 반영안됨!

3. 영속성의 4가지 상태

1. 흐름

내 그림

2. 비영속 (Transient)

  • 아직 JPA와 전혀 관계가 없는 상태
  • 단순히 메모리에만 존재하는 일반 객체 / new로 만든 객체
    • DB와 연결되지 않음
    • 영속성 컨텍스트에도 없음
    • 저장되기 전 단계

3. 영속 (Managed)

  • 영속성 컨텍스트가 해당 객체를 관리하는 상태
  • JPA를 사용하는 이유의 핵심이 되는 상태
    • 1차 캐시에 저장됨
    • 동일성 보장 (같은 객체 유지)
    • 변경 감지(dirty checking) 동작
    • 트랜잭션 커밋 시 DB 반영

4. 준영속 (Detached)

  • 원래는 영속 상태였지만, 현재는 영속성 컨텍스트에서 분리된 상태
    • 더 이상 JPA가 관리하지 않음
    • 변경 감지 동작 안 함
    • DB 자동 반영 안 됨

5.삭제 (Removed)

  • 삭제하기로 예약된 상태
    • 아직 DB에서 바로 삭제된 건 아님
    • 트랜잭션이 끝나면 DELETE 실행됨

3. 영속성 컨텍스트의 이점

1. 1차 캐시 (조회 성능 최적화)

  • 같은 트랜잭션 안에서 동일한 엔티티를 여러 번 조회하면 DB를 다시 조회X
  • 한 번 조회 → 영속성 컨텍스트에 저장
  • 이후 조회 → DB 안 가고 캐시에서 반환
  • 불필요한 SELECT 쿼리를 줄여버림

2. 동일성 보장 (Identity Guarantee)

  • 같은 ID로 조회한 엔티티는 항상 같은 객체처리
    1
    2
    3
    4
    
    User a = em.find(User.class, 1L);
    User b = em.find(User.class, 1L);
       
    //a == b (true)!
    

3. 변경 감지 (Dirty Checking)

  • 엔티티 값을 변경하면, JPA가 자동으로 UPDATE 쿼리를 생성
    1
    
     user.changeName("홍길동");
    

4. 쓰기 지연 (Write-behind)

  • INSERT / UPDATE / DELETE 쿼리를 바로 실행하지 않고 모아뒀다가 한 번에 실행
    • DB I/O 감소
    • batch 처리 가능
    • 성능 향상
  • 하지만, 트랜잭션이 끝난후에 커밋이 한번에 몰아칠 수 있어서 조심은 해야함.

5. 트랜잭션 단위 데이터 일관성

  • 트랜잭션 안에서는 항상 동일한 데이터를 보장
  • 같은 객체를 바라보고 처리하기 때문에 userA의 초기값과 나중값이 다를 수는 있음.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
     // 1. 유저 생성
     User userA = new User("userA");
     em.persist(userA);
    
     Long id = userA.getId();
    
     // 2. 값 변경 (UPDATE 쿼리 아직 안나감)
     userA.changeName("updatedUserA");
    
     // 3. 같은 엔티티 다시 조회
     User userB = em.find(User.class, id);
    
     // 4. 비교
     System.out.println(userA == userB); // true
     System.out.println(userB.getName()); // updatedUserA
    

4. Flush

1. flush

  • 영속성 컨텍스트의 변경 내용을 DB에 반영(동기화)하는 것
  • flush는 현재 트랜잭션 내부의 정합성을 맞추기 위한 것
  • 같은 트랜잭션 안에서도 DB 기준으로 조회해야 하는 순간 하는 작업

2. flush와 Commit

구분flushcommit
역할SQL 실행트랜잭션 확정
롤백 가능가능불가능
시점중간에도 가능마지막

3. 타이밍

1. 트랜잭션 커밋 직전

1
2
3
4
  @Transactional
  public void test() {
      user.changeName("A");
  }
  • 메서드 끝날 때 (commit 직전) 자동 flush
    • Dirty Checking 실행
    • SQL 생성 및 실행
    • 그다음 commit
  • 가장 기본 흐름
    • 아무것도 안 해도 마지막에 한 번은 무조건 flush

2. JPQL 실행 직전

1
2
 em.persist(user);
 em.createQuery("select u from User u").getResultList();
  • JPQL은 DB 기준으로 조회함
  • DB 상태를 최신으로 맞춰야 정확한 결과가 나오는 경우에 Flush

3. 직접호출

1
  em.flush();
  • 개발자가 강제로 플러쉬 진행!

4. Memo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   @Transactional
   public void example(EntityManager em) {
  
      em.persist(user1);  // SQL 안 나감
  
      user1.changeName("A"); // 메모리 변경
  
      // JPQL 조회 필요해서 flush 발생
      em.createQuery("select u from User u").getResultList();
  
      // 메소드 종료, 커밋 직전 → 다시 flush
      // 플러쉬로 보낼 데이터가 없다면 별도 쿼리 X
      // 플러쉬는 작업 흐름의 이름!  
   }
This post is licensed under CC BY 4.0 by the author.