[DB] 영속성 컨텍스트 (EntityManager)

2023. 1. 10. 19:55기술 창고/DataBase

728x90
SMALL

Spring 프로젝트에서 DB를 관리하고 운영하는 작업은 반드시 필요하다.

JPA가 대표적인 인터페이스 중 하나인데, JPA 내부 동작 원리를 알려면 영속성 컨텍스트를 반드시 알아야 한다.

 


영속성 컨텍스트

엔티티(도메인, DB테이블)를 영구 저장하는 환경이다.

애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 혹은 논리적 개념이다.

 

EntityManagerFactory에서 요청이 올 때마다 EntityManager를 생성한다.

EntityManager는 Factory와 다르게 여러 스레드에서 접근하면 동시성 문제가 발생한다.

따라서, EntityManager 가 생성되면 영속성 컨텍스트가 1:1로 생성된다.

EntityManager 를 통해서 영속성 컨텍스트에 접근한다.

 

 

엔티티 생명주기

 

  • 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
  • 영속(managed): 영속성 컨텍스트에 저장된 상태
  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed): 삭제된 상태

 

▶ 비영속

영속성 컨텍스트와 관계가 만들어지지 않은 새로운 상태

Footballer fooballer = new Footballer("메시");

 

 

▶ 영속

영속성 컨텍스트와 관계가 만들어져 관리되는 상태

Footballer fooballer = new Footballer("메시");

EntityManager em = EntityManagerFactory.createEntityManager();
em.persist(footballer); // 영속성 컨텍스트에서 Footballer 객체를 관리한다는 뜻.

em.persist(footballer) 를 했다고 해서 DB에 저장되는 것이 아니라 영속성 컨텍스트에서만 저장이 되어있는 것이다.

DB에 반영이 되려면 commit() 혹은 flush()를 통해서 반영이 되야한다.

 

 

▶ 준영속

영속성 컨텍스트에 저장되었다가 분리된 상태

Footballer fooballer = new Footballer("메시");

EntityManager em = EntityManagerFactory.createEntityManager();
em.detach(footballer); // 엔티티를 영속성 컨텍스트에서 분리해 준영속 상태로 만든다.

// or
em.clear(); // 영속성 컨텍스트 내부를 비우는 명령어로서 비워도 관리되던 엔티티는 준영속 상태가 된다.

// or
em.close(); // 영속성 컨텍스트를 종료해도 관리되던 엔티티는 준영속 상태가 된다.

준영속 상태에서는 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.

식별자 값을 가지고 있다.

 

 

▶ 삭제

엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한다.

em.remove();

 

 

 

영속성 컨텍스트 특징

1. 영속성 컨텍스트 식별자 값

영속성 컨텍스트는 엔티티를 식별자 값(@Id로 지정한 테이블의 기본키)으로 구분한다.

따라서 영속 상태에서는 식별자 값이 반드시 있어야 한다.

 

 

2. 1차 캐시

 

Footballer footballer = new Footballer();
EntityManager em = EntityManagerFactory.createEntityManager();

em.persist(footballer); // 영속성 컨텍스트에서 관리되게끔 요청을 하면 영속성 컨텍스트의 1차 캐시에 저장된다.
Footballer footballer2 = em.find(footballer.class, 1L) // 1차 캐시에서 1L의 값을 가지고 있는 데이터를 조회.

만약에 1차 캐시에 조회하고자 하는 데이터가 존재하지 않을 경우, DB에서 먼저 직접 조회하고 1차 캐시에 저장 후 반환한다.

요청이 시작되면 영속성 컨텍스트를 생성하고, 끝나면 영속성 컨텍스트를 지운다. 이 때 1차 캐시도 함께 삭제된다.

따라서, 애플리케이션 전체에서 공유하는 것이 아니다. 

(전체에서 공유하는 캐시는 2차 캐시이다.)

 

 

3. 동일성 보장

Footballer footballer2 = em.find(footballer.class, 1L);
Footballer footballer3 = em.find(footballer.class, 1L);
Footballer footballer4 = em.find(footballer.class, 1L);

// footballer2 == footballer3 == footballer4

em.find(footballer.class, 1L) 을 계속해서 호출해서 새로 객체를 생성을 해도 영속성 컨텍스트의 1차 캐시에 있는 같은 인스턴스를 반환하기 때문에 동일한 객체(엔티티)를 가지고 있다고 볼 수 있다.

따라서 동일성을 보장한다.

 

 

4. 트랜잭션 지원 쓰기 지연

em.persist(footballer2);
em.persist(footballer3);

persist를 사용하여 영속 상태로 만들 때 곧바로 Insert 쿼리가 데이터베이스로 가는 것이 아니라, 1차 캐시에 저장되면서 동시에 쓰기 지연 SQL 저장소에 넣어둔다.

 

transaction.commit();

commit 되는 순간 데이터베이스에 Insert 쿼리를 보내어 반영된다.

flush() 를 호출하면 커밋과는 별도로 데이터베이스에 반영되어 저장될 수 있다.

 

 

5. 변경 감지 (Dirty Checking)

Footballer footballer2 = em.find(footballer.class, 10L);
footballer2.setName("데용");

transaction.commit(); // Update 쿼리 발생
// 혹은 @Transactional 을 등록하여 데이터 변경 쿼리를 실행 혹은 flush() 를 실행

JPA는 변경 감지라는 자동적으로 엔티티를 변경할 수 있는 기능이 있다.

 

JPA는 트랜잭션이 되는 순간 내부적으로 flush()가 호출된다.

그 때 엔티티와 1차 캐시 내의 스냅샷(최초 상태)를 비교한다.

비교했을 때, 변경점이 있다면 Update 쿼리를 쓰기 지연 SQL 저장소에 저장한다.

commit()이 되면 flush()가 호출되어 DB에 Update 쿼리가 나가서 반영된다.

 

 

6. 지연 로딩 (Lazy Loading)

서로 연관관계가 매핑되어있는 엔티티의 조회 시, 프록시를 반환함으로써 바로 조회하는 것이 아니라 진짜 필요할 떄 쿼리를 발생시켜 조회되게끔하는 기능이다.

 

 

 

# 여기서 자주 나오는 flush란?

영속성 컨텍스트의 변경 내용을 최종적으로 데이터베이스에 반영하는 기능이다.

 

flush가 발생하게 되면 더티 체킹(변경 감지), 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록, 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송하여 반영한다. (등록, 수정, 삭제 쿼리에 해당되며 단순 조회는 관련이 없다.)

 

영속성 컨텍스트를 flush 하는 방법으로는

1. em.flush()

2. 트랜잭션 commit

3. JPQL 쿼리 실행

 

flush를 하면 반영은 하지만 영속성 컨텍스트 내부를 비워주지는 않기 때문에 flush를 하고 clear를 통해 비워내 주거나 다른 후처리를 해주어야 한다.

 

728x90
반응형
LIST