[SpringBoot] 영속성 컨텍스트

LaStella·2023년 4월 11일

EntityManagerFactory, EntityManager

Entity

RDB의 Table과 매핑되는 객체입니다.

@Entity
@Getter
@Setter
@Table(name = "customers")
public class Customer {
    @Id
    private long id;
    private String firstName;
    private String lastName;
}

EntityManagerFactory

  • Entity를 관리하는 EntityManager를 생산하는 공장입니다.
  • Thread Safe 합니다. 하나의 빈에 여러 쓰레드가 접근해도 문제가 없습니다.

EntityManager

  • EntityManager는 Entity를 저장하고, 수정하고, 삭제하고, 조회하는(CRUD) 등 Entity와 관련된 모든 일을 처리합니다.
  • Thread Safe 하지 않습니다. 여러 쓰레드에서 동시에 접근할 경우 동시성 이슈가 발생할 수 있습니다.
    • 엔티티 매니저 팩토리에서 새로운 엔티티 매니저를 생성하며 단일 쓰레드가 접근하도록 하는 전략을 사용해야합니다.

이미지출처-프로그래머스 강의

엔티티 매니저 팩토리는 각 요청에 대해 엔티티 매니저를 생성합니다.
엔티티 매니저는 DB 커넥션 풀을 관리하는 객체로부터 커넥션을 가져와 DB와 연산작업을 합니다.
엔티티 매니저는 트랜잭션을 시작할 때, 커넥션을 획득합니다.(DB에 실제로 커밋이 발생할때 커넥션을 가져와 사용합니다.)

영속성 엔티티

영속선 컨텍스트

  • JPA를 이용하는데 가장 중요한 요소입니다.
  • 엔티티를 영구 저장하는 환경이라는 뜻입니다.
  • 엔티티매니저는 엔티티를 영속성 컨텍스트에 보관하고 관리합니다.

이미지출처-프로그래머스 강의

엔티티는 엔티티 매니저에 의해 영속성 컨텍스트에서 관리됩니다.
persist메소드 : 영속화 하겠다는 의미입니다. 즉 엔티티 객체를 영속성 컨텍스트 안에서 관리를 한다는 의미입니다.

영속성 컨텍스트의 특징

  • 영속성 컨텍스트와 식별자 값
    • 영속성 컨텍스트 안에서 관리되는 엔티티는 식별자 값을 반드시 가져야합니다.
    • key-value로 엔티티를 관리하기 때문입니다. (1차 캐시를 key-value형태로 관리)
  • 영속성 컨텍스트와 데이터 베이스 저장
    • JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 DB에 반영합니다. (FLUSH)
    • 플러시(flush)는 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 작업인데, 이때 등록, 수정, 삭제한 엔티티를 DB에 반영한다.
  • 영속성 컨텍스트가 엔티티를 관리함으로 얻는 이점
    • 1차 캐시
    • 동일성 보장
    • 트랙잭션을 지원하는 쓰기 지연
    • 변경 감지
    • 지연 로딩

엔티티 생명 주기

이미지출처-프로그래머스 강의

  • 비영속 (new / transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
  • 영속 (managed) : 영속성 컨텍스트에 저장된 상태
    • 영속성 컨텍스트 안에서 관리되므로 엔티티가 변경되면 영속성 컨텍스트가 감지하고 DB에 FLUSH합니다.
  • 준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제 (removed) : 삭제된 상태

비영속

// 객체가 영속성컨텍스트, 데이트베이스와 무관한 상태입니다.
Customer customer = new Customer();
customer.setId(1L);
customer.setFirstName("honggu");
customer.setLastName("kang");

영속

Customer customer = new Customer();
customer.setId(1L);
customer.setFirstName("honggu");
customer.setLastName("kang");

// customer객체가 영속성 컨텍스트에서 관리됩니다.
// em == EntityManager
em.persist(customer);

준영속

// 영속상태의 customer객체 (엔티티)를 영속성컨텍스트에서 분리합니다.
em.detach(Customer);
// 영속상태의 모든 객체를 영속성컨텍스트에서 분리합니다.
em.clear()
// 영속성컨텍스트를 종료합니다.
em.close()

삭제

// customer 엔티티를 영속성컨텍스트에서 분리하고, DB에서도 삭제합니다.
em.remove(customer)

Customer 엔티티를 통한 영속성 컨텍스트 이해

저장

EntityManager em = emf.createEntityManager(); // 1)엔티티 매니저 생성
EntityTransaction transaction = em.getTransaction(); // 2) 트랜잭션 획득
transaction.begin(); // 3) 트랜잭션 begin

Customer customer = new Customer(); / 4-1) 비영속
customer.setId (1L);
customer.setFirstName("honggu");
customer.setLastName("kang");

em.persist(customer); // 4-2) 영속화

transaction.commit(); // 5) 트랜잭션 commit 
//트랜잭션이 커밋이 되는 순간 쿼리가 수행됩니다. (flush연산으로 DB와 동기화가 됩니다.)

이미치출처-프로그매러스 강의

조회

1차 캐시를 이용한 조회

@Test
void 조회_1차캐시_이용() {
	EntityManager em = emf.createEntityManager();
	EntityTransaction transaction = em.getTransaction() ;
	transaction.begin();
    
	Customer customer = new Customer();
	customer.setId (1L);
	customer.setFirstName("aaa");
    customer.setLastName("bbb");
    
	em.persist(customer);
    transaction.commit;
    
	Customer entity = em.find(Customer.class, 1L); // 1차 캐시에서 조회합니다.
	log.info("{} {}", entity.getFirstName(), entity.getLastName());
}

이미지 출처-프로그래머스 강의

DB에 질의하지 않고 1차 캐시에서 결과를 반환합니다.

DB를 이용한 조회

@Test
void 조회() {
	EntityManager em = emf.createEntityManager();
	EntityTransaction transaction = em.getTransaction() ;
	transaction.begin();
    
	Customer customer = new Customer();
	customer.setId (2L);
	customer.setFirstName("ccc");
    customer.setLastName("ddd");
    
	em.persist(customer);
    transaction.commit;
    
    em.clear(); // 영속성 컨텍스트를 초기화 합니다.
    
	Customer entity = em.find(Customer.class, 2L); // DB에서 조회합니다. SELECT 쿼리가 수행됩니다.
	log.info("{} {}", entity.getFirstName(), entity.getLastName());
    em.find(Customer.class, 2L); // 1차 캐시를 사용하므로 SELECT 쿼리가 수행되지 않습니다. 
}

이미치출처-프로그매러스 강의

DB에 질의하여 1차 캐시에 저장한 후에 결과를 반환합니다.

수정

@Test
void 수정() {
	EntityManager em = emf.createEntityManager();
	EntityTransaction transaction = em.getTransaction() ;
	transaction.begin();
    
	Customer customer = new Customer();
	customer.setId (1L);
	customer.setFirstName("aaa");
    customer.setLastName("bbb");
    
	em.persist(customer);
    transaction.commit;
    // 엔티티를 영속화 한 후, 커밋을 해서 flush()를 통해 DB에 저장합니다.
    
    transaction.begin();
    
	Customer entity = em.find(Customer.class, 1L);
    entitiy.setFirstName("ccc");
    entitiy.setLastName("ddd");
    
    // em.update(entity)를 할 필요가 없습니다.
    transaction.commit(); // 여기서 flush()를 통해 UPDATE 쿼리가 수행됩니다.
}

이미지 출처-프로그래머스 강의

[변경감지 - dirty checking]
JPA 는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해 두는데 이것을 스냅샷이라 합니다. 그리고 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾습니다. 만일 스냅샷과 비교하여 변경된 내용이 있을 경우 update Query를 수행합니다. (변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용됩니다.)

삭제

@Test
void 삭제() {
	EntityManager em = emf.createEntityManager();
	EntityTransaction transaction = em.getTransaction() ;
	transaction.begin();
    
	Customer customer = new Customer();
	customer.setId (1L);
	customer.setFirstName("aaa");
    customer.setLastName("bbb");
    
	em.persist(customer);
    transaction.commit;
    
    transaction.begin();
    
	Customer entity = em.find(Customer.class, 1L);
    em.remove(enmtity); // flush()를 통해 DELETE 쿼리가 수행됩니다.
}
profile
개발자가 되어가는 중...

0개의 댓글