개발하는 자몽

[TIL / JPA] 영속성 컨텍스트 본문

Java

[TIL / JPA] 영속성 컨텍스트

jaamong 2024. 6. 29. 09:47

이 글은 아래 강의를 바탕으로 공부한 내용을 정리하는 글입니다.

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 | 김영한 - 인프런

김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도

www.inflearn.com

 

🔖목차🔖

  1. 영속성 컨텍스트
  2. 엔티티의 상태
  3. 1차 캐시
  4. 엔티티 등록 - 쓰기 지연
  5. 변경 감지(Dirty Checking)
  6. SUMMARY

 


 

 

 

 

 

영속성 컨텍스트

  • 엔티티를 영구 저장하는 환경
  • 논리적 개념, 눈에 보이지 않음
  • `EntityManager`를 통해서 접근
    • `EntityManager`는 엔티티를 조작하고 데이터베이스와의 통신을 수행하는 인터페이스. 엔티티의 영속성 컨텍스트를 관리하며, 데이터베이스로의 변경사항을 자동으로 동기화함.
  • `EntityManager`를 생성하면 그 안에 영속성 컨텍스트가 1:1로 생성됨

예제 코드

import jakarta.persistence.*;

import java.util.List;

public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); 
        EntityManager em = emf.createEntityManager(); 
        
        EntityTransaction tx = em.getTransaction(); //JPA는 반드시 트랜잭션 안에서 작업
        tx.begin();

        try {
            ...
            em.persist(entity);
            tx.commit(); //정상 종료가 되면 커밋
        } catch (Exception e) {
            tx.rollback(); //문제가 생기면 롤백
        } finally {
            em.close(); //작업 종료가 되면 entity manager 닫기
        }

        emf.close(); //애플리케이션이 모두 종료되면 entity manager factory 닫기
    }
}
  • `EntityManagerFactory`는 애플리케이션(DB) 당 1개만 생성해야 함
  • `EntityManager`는 고객 요청이 올 때마다 쓰고 닫아야 함
    • 주의! 스레드 간에 공유 X → 장애 발생. 사용하고 버려야 함.
  • `em.persist(entity)`
    • 영속성 컨텍스트를 통해서 엔티티를 영속화하는 코드
    • 엔티티를 DB에 저장하는 것이 아닌 영속성 컨텍스트에 저장하는 것
  • `EntityManager`는 데이터베이스 커넥션을 가지고 동작하기 때문에 작업이 끝나면 반드시 닫아줘야 함

엔티티의 상태

비영속

객체 생성 후 `EntityManager`에 넣지 않음. JPA와 전혀 관계없는 상태.

 

영속

`EntityManager`안에는 영속성 컨텍스트가 있다. `EntityManager.persist(Entity)`를 하면 해당 엔티티가 영속성 컨텍스트에 들어가면서 영속 상태가 된다.

        try {
            // ---비영속 상태---
            Member member = new Member();
            member.setId(1L);
            member.setName("member");

            // --- persist -> 영속 상태 ---
            System.out.println("=== BEFORE ===");
            em.persist(member); //영속성 컨텍스트를 통해서 member 객체가 관리됨. 여기서 insert 쿼리 안나감
            System.out.println("=== AFTER ===");

            tx.commit(); //영속 상태가 되고 바로 DB에 저장되는 것이 아닌 트랜잭션을 커밋하는 시점에 영속성 컨텍스트에 있는 쿼리가 DB로 나감
        }
        ...

 

준영속 상태

객체를 아래 코드처럼 영속성 컨텍스트에서 지우면(detach) 아무런 관계가 없게 됨

EntityManager.detach(Entity);

 

Note  em.persist()를 해도 영속 상태가 되고, JPA를 통해 조회(em.find()) 했을 때 해당 객체가 영속성 컨텍스트에 없어서 1차 캐시에 넣는 것도 영속 상태가 되는 것

 

1차 캐시

영속성 컨텍스트는 내부에 1차 캐시를 가지고 있다. 예를 들어 객체를 생성한 상태는 비영속, `EntityManager.persist(Entity)`를 하면 영속 상태가 된다. 이렇게 `EntityManager`를 통해 영속성 컨텍스트에 넣으면 내부에 존재하는 1차 캐시에 엔티티를 넣어둔다.

 

1차 캐시에서 조회

Member member = new Member();
member.setId("member1");
member.setName("회원1");

//1차 캐시에 저장
EntityManager.persist(member); 
//1차 캐시에서 조회
EntityManager.find(Member.class, "member1");

`EntityManager.find()`를 하면 DB에서 찾는 것이 아닌 먼저 1차 캐시에서 찾는다.

 

  • 1차 캐시에 찾는 값이 존재하면 DB가 아닌 여기에서 값을 조회한다.
  • 1차 캐시에 없으면 DB에서 조회하고 1차 캐시에 저장한다.
    • 이후 해당 객체를 찾으면 1차 캐시에서 조회할 수 있다.

`EntityManager`는 DB 트랜잭션 단위로 생성되고 트랜잭션이 종료되면 EntityManager도 같이 종료된다. 즉, 고객 요청이 들어와서 비즈니스 로직이 끝나면 해당 영속성 컨텍스트를 지운다는 의미이다. 따라서 1차 캐시도 사라지게 된다. 이러한 찰나의 순간에서만 이점이 있다.

 

엔티티 등록 - 쓰기 지연

영속성 컨텍스트에는 `1차 캐시` 외에  `쓰기 지연 SQL 저장소`라는 것이 있다.

`EntityManager.persist(entity1)`를 하면 1차 캐시에 `entity1`가 들어간다. 동시에 JPA가 해당 엔티티를 분석하여 `INSERT` 쿼리를 생성하고 이를 쓰기 지연 SQL 저장소에 쌓아둔다.

`EntityManager.persist(entity2)`를 하면 1차 캐시에 `entity2`가 들어간다. 동시에 JPA가 해당 엔티티를 분석하여 `INSERT` 쿼리를 생성하고 마찬가지로 이를 쓰기 지연 SQL 저장소에 쌓아둔다.

저장소에 있던 쿼리들은 트랜잭션을 커밋하는 시점에 DB로 날아간다(flush).

 

쓰기 지연 SQL 저장소에 원하는 만큼 쿼리를 모아서 한 번에 DB에 보내는 방법도 있다. 이를 `JDBC Batch`라고 한다. 하이버네이트에는 아래와 같은 옵션이 있다. 

spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 100

이렇게 하면 해당 사이즈만큼 쿼리를 모아서 한 번의 네트워크로 DB에 쿼리를 보낸다. (버퍼링 같은 기능)

 

변경 감지(Dirty Checking)

JPA는 DB 트랜잭션 커밋 시점에 영속성 컨텍스트 내부에 있는 `flush()`라는 것이 호출된다. 이때 엔티티와 스냅샷을 비교한다.

1차 캐시 안에는 PK인 `@Id`와 `Entity`, `스냅샷`이 있다. 스냅샷은 값이 영속성 컨텍스트에 들어온 최초 시점의 상태를 저장해 둔 것이다.

해당 상태에서 엔티티의 값이 변경되면 트랜잭션 커밋 시점에 `flush()`가 호출되면서 JPA가 Entity와 스냅샷을 비교한다. 비교하고 값이 변경되었으면 `UPDATE` 쿼리를 쓰기 지연 SQL 저장소에 둔다. 그리고 해당 쿼리를 DB에 반영하고 커밋하게 된다.

 

flush

`flush`는 영속성 컨텍스트의 변경 내용을 DB에 반영하는 것으로, 영속성 컨텍스트의 쿼리들을 DB에 날리는 것이다. 

 

`flush`가 발생하면 아래와 같은 일이 일어난다.

 

  1. DB 트랜잭션이 커밋되면 `flush`가 자동으로 발생
  2. 변경 감지(dirty checking)
  3. 수정된 엔티티와 관련된 update 쿼리를 쓰기 지연 SQL 저장소에 등록
  4. 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송(등록, 수정, 삭제 쿼리 등)

Note  flush를 해도 1차 캐시는 유지된다. 영속성 컨텍스트의 변경내용을 DB에 동기화하는 것뿐.

 

SUMMARY - 배운 것 조합하기

`em.persist()`를 하고 `em.find()`를 바로 호출하면, 영속성 컨텍스트에 있는 데이터를 가지고 오기 때문에 조회 쿼리를 볼 수 없다. 이때 `em.flush()`, `em.clear()`를 하면 DB에 데이터를 반영하고, 영속성 컨텍스트를 초기화한다. 따라서 `em.find()`를 호출하면 영속성 컨텍스트에 데이터가 없으므로 DB에서 데이터를 조회하게 된다. 

 

 

 

 

Comments