본문 바로가기
BackEnd

[JPA] 영속성 컨텍스트

by mizuiro 2024. 8. 29.

영속성 컨텍스트(Persistence Context)는 객체와 데이터베이스 사이의 매핑을 관리하는 엔티티 매니저(Entity Manager)의 일종의 캐시 영역

이는 자바 ORM(Object-Relational Mapping) 기술인 JPA(Java Persistence API)에서 핵심적인 역할을 수행합니다.

영속성 컨텍스트는 엔티티의 생명주기를 관리하며, 데이터베이스와의 동기화를 통해 데이터 일관성을 유지합니다.

1.1 Entity와 Entity 관리

Entity

데이터베이스의 테이블과 매핑되는 객체(자바 클래스)

@ Entity 사용

Entity Manger

엔터티의 생명주기와 밀접한 관련성

⇒ 프로젝트의 복잡성이

Entity의 생명주기

영속성 컨텍스트의 특징/ 이점

  1. 1차 캐시 (First Level Cache):
    • 영속성 컨텍스트는 엔티티를 메모리에 캐시하여 동일한 엔티티를 반복적으로 데이터베이스에서 조회하지 않도록 합니다. 이 캐시는 같은 트랜잭션 내에서만 유효합니다.
  2. 엔티티 동일성 보장 (Entity Identity):
    • 동일한 영속성 컨텍스트 내에서는 동일한 데이터베이스 레코드를 조회할 때마다 동일한 엔티티 인스턴스가 반환됩니다.
  3. 변경 감지 (Dirty Checking):
    • 영속성 컨텍스트는 트랜잭션이 종료될 때 엔티티의 변경 사항을 자동으로 감지하고, 필요한 경우 데이터베이스에 반영합니다.
  4. 쓰기 지연 (Write Behind):
    • 트랜잭션이 커밋되기 전까지 변경된 데이터를 데이터베이스에 즉시 반영하지 않고, 커밋 시점에 일괄적으로 반영하여 성능을 최적화합니다.
  5. 트랜잭션 범위:
    • 영속성 컨텍스트는 일반적으로 트랜잭션 범위와 일치합니다. 트랜잭션이 시작되면 새로운 영속성 컨텍스트가 생성되고, 트랜잭션이 종료되면 해당 컨텍스트가 닫힙니다.

영속성 컨텍스트의 과정

  1. 엔티티 매니저 생성:
    • 애플리케이션이 시작되면 EntityManagerFactory를 통해 EntityManager를 생성합니다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("example-unit");
EntityManager em = emf.createEntityManager();

  1. 트랜잭션 시작:
    • 엔티티 매니저를 통해 트랜잭션을 시작합니다.
    em.getTransaction().begin();
    
  2. 엔티티 조회:
    • find 또는 query 메소드를 사용하여 엔티티를 조회합니다. 조회된 엔티티는 영속성 컨텍스트에 캐시됩니다.
    MyEntity entity = em.find(MyEntity.class, entityId)
    
  3. 엔티티 변경:
    • 엔티티의 속성을 변경하면 영속성 컨텍스트가 이를 감지합니다.
    entity.setName("New Name");
    
  4. 엔티티 저장:
    • persist 메소드를 사용하여 새로운 엔티티를 영속성 컨텍스트에 추가합니다.
    em.persist(newEntity);
    
  5. 트랜잭션 커밋:
    • 트랜잭션을 커밋하면, 영속성 컨텍스트는 변경된 엔티티를 데이터베이스에 반영합니다. 이 과정에서 변경 감지(dirty checking)를 통해 수정된 엔티티만 업데이트됩니다.
    em.getTransaction().commit();
    
  6. 영속성 컨텍스트 종료:
    • 트랜잭션이 종료되면 영속성 컨텍스트도 종료됩니다. EntityManager를 닫아야 합니다.
    em.close();
    
    

수업 내용

영속성 컨텍스트 & 엔터티 관리

영속성 컨텍스트

  • 컨텍스트는 컨테이너라는 의미
    • 엔터티를 저장해서 관리하는 공간 또는 영역
  • 컨테이너의 핵심 기능 : persistence.xml의 메타 정보를 참조하여 애플리케이션 운영에 필요한 객체를 생성하고 관리
  • 영속성 컨테이너는 persistence.xml의 메타 정보를 참조하여 메타 정보를 로딩하고, Entity Manger 객체를 생성할 때 자동으로 생성
  • Entity Manger가 제공하는 메소드를 통해서만 접근이 가능
  • 영속성 컨테이너가 관리하는 엔터티의 상태는 생성, 관리, 분리, 삭제 의 네가지 상태로 구분

관리 상태

  • 엔터티를 연속성 컨테이너에 등록
  • Entity Manger의 persist() : 엔터티 생성 → 컨테이너에 등록
  • Entity Manger의 find() :
    • DB 조회 → 엔터티 생성 → 컨테이너에 등록 → 요청한 쪽에 엔터티를 전달
    • find() 메소드를 사용한다고 해서 항상 DB에서 조회하는 것은 아니다
    • 영속성 컨테이너에서 먼저 조회 후 DB에서 조회
  • Entity 가 DB에 저장(insert)
    • 반드시 transaction 안에서 persist() 호출이 되어야 함

분리 상태

  • 영속 컨테이너에 있던 엔터티가 특정 작업에 의해서 영속 컨테이터를 벗어난상태
  • 엔터티를 수정해도 데이터베이스에 반영이 되지 않음 ⇒ Dirty Checking 을 수행하지 않음
  • 생성상태와 분리 상태의 차이점
    • 생성 상태 : persist() 를 호출하지 않음 ⇒ 식별자가 없는 상태
    • 분리 상태 : persist() 를 호출한 적이 있음 ⇒ 식별자가 있는 상태
  • 분리 상태 전환을 위한 Entity Manger 의 메소드
    • detach(entity) : 특정 Entity 만 분리
    • clear() : 컨테이너 초기화 ⇒ 영속 컨테이너가 관리하는 모든 엔티티를 분리
    • close() : 컨테이터 종료 ⇒ Manger 가 관리하는 entity 가 있다면 먼저 비영속 상태로 모두 전환 후, 종료

JPA 사용시 중요한 2가지 부분

  • 설계 부분 : JPA 엔터티와 테이블 매핑
  • 사용 부분 : 실제 사용 부분

영속성 컨텍스트

엔터티를 영구 저장하는 환경 ⇒ DB에 저장되는 것은 아니다

EntityManger를 통해 저장

  • 장점
    • 1차 캐시, 동일성 보장, transaction 지원, 쓰기 지원, 변경 감지
  • 1차 캐시
    • find()
      • → 영속성 컨텍스트에서 조회(1차 캐시에서 조회 ⇒ DB에 요청하지 않아도 됨)
      • → DB에서 조회(1차 캐시에 저장)
  • 동일성 보장Employee emp2 = em.find(Employee.class, 1L);영속성 컨텍스트에서 식별자로 관리되는 엔터티는 동일
  • emp1 == emp2 ? ⇒ true 반환
  • Employee emp1 = em.find(Employee.class, 1L);
  • transaction
    • (JPA 에서의 transaction) 변경된 사항 ⇒ 쓰기 지연

⇒ 트랜잭션을 지원하는 쓰기 지연

트랜잭션을 커밋을 할 때 모아둔 모든 쿼리들을 한번에 데이터베이스에 전송

(쓰기 지연 SQL 저장소)

  • 변경 감지 (Dirty Checking)
    emp.setName(”이름변경”)
    transaction.commit()
    
    개발자에게는 update sql 작성을 하지 않아도 됨→ update sql 을 작성해서 쓰기 지연 SQL 저장소에 저장
  • → 데이터베이스에 전송
  • 스냅샷(원본)과 변경된 엔터티(1차 캐시에 저장된)를 비교
  • 영속성 관리 상태의 엔터티
  • flush()영속성 컨텍스의 변경 내용을 테이터베이스에 반영하는 작업을 수행작성된 SQL은 쓰기 지연 SQL 저장소에 저장이 되고 데이터베이스에 전송
  • 영속성 컨텍스트에 있는 모든 엔터티를 스냅샷과 비교해서 수정이 발생된 엔터티를 찾고, 해당 수정에 맞는 SQL 이 작성된다
  • 트랜잭션 커밋 시 flush() 호출

Entity 영속성 상태 관리

항상 데이터를 조회한다고 해서 DB에서 조회하는 것이 아니다

  • transaction 바깥에서 entity를 변경할 경우 Entity Manger에는 반영이 되지만 DB에는 반영이 되지 않음
    • ex) find() 메서드 사용 1
      1. transaction 바깥에서 employee.setName()으로 entity 값을 변경한다transcation.commit() → employee.setName() → em.find() → DB에는 반영이 되지 않음
      2. entity 변경된 값은 transaction에 반영되지 않고 Entity Manger 만 값 변경을 가지고 있다
      3. find() 메소드 사용 2 (새로운 thread)entity manger 가 조회할 수 있는 1차 캐시에는 데이터에이스 값이 생성 된 것이 없으므로 아무 데이터가 없다
      4. 그러므로 사용자의 find() 메서드 요구를 찾기 위해 entity manger는 DB에서 데이터를 가져와야 하고 select 문을 실행하여서 데이터를 가져와 영속성 컨테이너)에 저장하고 사용자의 find() 메서드를 실행한다
      5. 새로운 entity manger를 생성해서 기존에 있던 테이블의 값을 조회를 한다
      find() → context 내에서 검색 → select 문장 생성
  • 준영속성 상태에서의 데이터 상태
    • detach() 로 변경된 entity에게 데이터 수정을 하였을 때 DB에 반영이 되지 않는다
    • 준영속성 상태에서 find() 메소드로 데이터를 조회를 하면 (영속성 컨텍스트)가 존재하지 않으므로 DB에서 select 문으로 들고와서 엔터티에 할당을 하고 다시 영속 상태인 entity를 entity Manger 가 관리를 한다 (즉, 영속성 컨테이너 생성)
  • 삭제 상태에서의 데이터 상태
    • 생성한 entity 를 entity Manger를 사용하여 remove()를 해주면 entity가 삭제가 된다
      • 삭제라는 말은 단순히 entity 객체가 없어지는 것이 아니라 DB에서 entity 객체와 함께 할당이 된 table이 삭제가 되는 것이다
      • 중요!!!!!! =⇒ 삭제는 DB 삭제도 같이 하는 것이다
  • 준영속 상태에서 다시 영속 상태로의 변환
    • detach()로 준영속 상태에 들어간 entity는 데이터라고 할 수 없고 단순힌 자바의 클래스 상태
    • 이것을 다시 entity manger로 영속 상태로 만들어 주기 위해 merge() 메서드 사용
    • 클래스로 전환된 이전 entity 인스턴스를 merge() 메서드에 넣어주고 entity로 전환된 인스턴스를 받아 이전 entity 에 할당을 해줌
    • EntityManger em = new EntityManger(); Employee employee = new Employee(); // em.detach(employee); // 일반 인스턴스 객체로 변한 employee에 다시 영속 상태가 된 인스턴스를 할당 employee = em.merge(employee);
    • 조심할 것!!!
      • 만약 준영속으로 바뀐 후에 entity 클래스를 수정을 한 후에 다시 merge()로 영속 상태로 전환을 하면 merge()는 DB에 반영되어진 데이터베이스의 값을 가져오기 때문에 준영속 상태에서 수정한 값의 변화는 없어진다
  • 영속 → 준영속 → 영속 으로 상태 변화createEntity() : 새로운 영속 컨텐스트를 생성하고 엔터티를 영속 상태로 만든뒤 준영속으로 변환
    • 순서
      1. createEntity() 로 영속 → 준영속 엔터티 생성 후 준영속 엔터티 반환
      2. 준영속 엔터티의 데이터 값 변경 (setName())
      3. 준영속 엔터티를 mergeEntity()로 영속 엔터티로 변환한 새로운 인스턴스를 생성하여 같은 내용 두가지 버전의 엔터티가 생성됨
  • mergeEntity() : 준영속 상태인 엔터티를 관리하기 위해 또다른 영속 컨텍스트를 생성하고 준영속 상태 엔터티로 영속 상태 엔터티로 재생성하여 같은 내용 다른 버전의 두가지 엔터티가 생성되게 하는 함수
  • 두개의 함수 존재

!!!! 중요

이때 엔터티는 값을 저장해놓고 있어서 이 값을 바로 보는 것이 아니라 테이블에 어떠한 값이 정의되어 있다고 명시해놓은 설명서 같은 것이다

또한 엔터티는 하나의 식별자가 가리키는 값들만 저장하고 있다

여러개를 저장하려고 하면 리스트로 엔터티들을 저장해야 한다

이 개념을 생각하고 이 부분을 읽을 것 !!!!!!!!!!!!!!!

  • mergeEntity() 분석
    1. merge() 호출 : 준영속 상태의 변경이 발생한 엔터티를 매개변수로 전달
    2. 새롭게 생성된 영속성 컨테이너에는 매개변수로 받은 엔터티의 테이블이 없는 상태
      1. 1차 캐시는 매개변수로 받은 엔터티와 연결된 테이블의 값이 존재하지 않으므로 데이터베이스로 가서 엔터티가 정의한 테이블 값을 가져와야 함
      2. ⇒ select 문장이 작성되고, 전송이 됨
      3. DB 검색 결과를 바탕으로 테이블 값을 생성하고 1차 캐시에 저장
      4. 1차 캐시에 저장된 테이블 값을 매개변수로 받은 준영속 상태 엔터티에서 정의한 것과 비교하여 준영속 상태 엔터티에서 변경이 발생한 부분을 반영
      5. 새로운 영속 엔터티의 name 은 “홍길동” 이고,따라서, “홍길동”을 “이름 변경”으로 수정해야 함
      6. ⇒ update 문장이 작성되고, DB로 전송이 됨
      7. 준영속 상태의 엔터티 name의 값은 “이름변경 “ 인 상태
    3. 매개변수로 전달된 준영속 엔터티를 식별자 값으로 1차 캐시에서 엔터티 검색을 수행
    4. 모든 변경 사항이 반영이 되어 동기화가 이루어진 상태
    5. 최종적으로 수정사항이 모두 반영된 영속 엔터티 반환
    6. 현재 두가지 entity 가 공존하고 있고, entity 와 데이터의 영속 및 동기를 위해 merge() 가 반환하는 entity를 사용해야 함

'BackEnd' 카테고리의 다른 글

도커와 쿠버네티스 -Docker 2 (Config Server)  (0) 2024.10.15
쿠버네티스 기초  (0) 2024.10.01
[JPA] Framework  (0) 2024.08.26
[JPA] Spring과 JPA-3  (0) 2024.07.31
[JPA] Spring과 JPA-2  (0) 2024.07.30