[Spring] JPA 영속성 컨텍스트와 EntityManager를 이용한 CRUD

구범모·2023년 7월 26일
0

EntityManagerFactory

  • Entity를 관리하는 EntityManager를 생산하는 팩토리.
  • Thread Safe하다.

EntityManager

  • EntityManager는 Entity를 저장하고, 수정하고, 삭제하고, 조회하는 (CRUD) 등 Entity와 관련된 모든 일을 처리한다.
  • Thread Safe 하지 않다. 여러 Thread에서 동시에 접근할 경우 동시성 이슈가 발생할 수 있다.

영속성 컨텍스트

  • Entity Mnager에서 Entity를 관리할 수 있는 공간.
  • 이 공간을 거져 실제 DB에 저장할 수 있다.
  • key-value 형태로 entity 관리.
  • transaction 커밋을 하면, 쓰기 지연 저장소에 있는 쿼리를 수행하여 컨텍스트에 새로 저장된 엔티티를 DB에 반영.
  • transaction 커밋을 하면, 엔티티매니저 내부적으로 flush메소드 호출.

영속성 컨텍스트의 구성 요소

  • 스냅샷 : 엔티티가 처음 영속성 컨텍스트에 들어왔을 때의 정보 저장.
  • 1차 캐시 : Key - Value 형태로 entity 관리.
  • 쓰기 지연 저장소 : DB에 수행 할 쿼리들을 모아놓는 공간.

엔티티의 생명주기

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

엔티티매니저를 이용한 CRUD 연산

EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();

transaction.begin();

Customer customer = new Customer(); // 비영속상태
customer.setId(1L);
customer.setFirstName("firstName");
customer.setLastName("lastName");

entityManager.persist(customer); // 비영속 -> 영속 (영속화)
transaction.commit(); // entityManager.flush() 호출.

persist()메소드를 호출하여 customer객체를 영속화와 동시에 쓰기 지연 저장소에 INSERT쿼리 저장.

이후 transaction.commit으로 쓰기 지연 저장소의 INSERT쿼리가 실행되어 DB저장까지 이루어 진다.

entityManager.find(Customer.class, 1L);

위의 저장 코드가 실행된 이후, 영속성 콘텍스트 안에는 1L이라는 id를 갖는 customer가 1차 캐시 내부에서도 존재한다. 따라서 entityManger.find(~, 1L)을 호출하면 DB에서 조회하는 것이 아닌, 1차캐시에서 해당 엔티티를 조회한다. → 조회속도가 빨라지는 이점이 있다.

EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();

transaction.begin();

Customer customer = new Customer(); // 비영속상태
customer.setId(1L);
customer.setFirstName("firstName");
customer.setLastName("lastName");

entityManager.persist(customer); // 비영속 -> 영속 (영속화)
transaction.commit(); // entityManager.flush() 호출.

entityManger.clear(); // 영속성 콘텍스트를 비운다 -> 1차캐시도 비워진다.

Customer dbCustomer = entityManager.find(Cusotmer.class, 1L); // 1차캐시에 1L이라는 id를 갖는 엔티티가 없으므로, db까지 들어가서 조회한다.
EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();

transaction.begin();

Customer customer = new Customer(); // 비영속상태
customer.setId(1L);
customer.setFirstName("firstName");
customer.setLastName("lastName");

entityManager.persist(customer); // 비영속 -> 영속 (영속화)
transaction.commit(); // entityManager.flush() 호출.

--------------------------- // 하나의 트랜잭션 종료.

transaction.begin();

Customer cacheCustomer = entityManager.find(Cusotmer.class, 1L);
cacheCustomer.setFirstName("updatedFirstName");

transaction.commit();

업데이트 과정

하나의 customer를 만들고 저장을 할 때, 다시말해 처음 영속성 컨텍스트에 저장될 때, 스냅샷이라는 것이 생성된다.

이후 해당 엔티티를 조회하여 필드값을 바꾼 이후 commit을 한다.

이때 따로 update메소드를 호출하지 않아도, transaction이 커밋되는 시점(flush가 호출되는 시점)에 스냅샷과 커밋시점의 엔티티를 비교하여 변경된 내용이 있다면, 자동적으로 update Query가 수행된다. 이것을 dirty checking이라 하는데, 쉽게 말해 commit 시점에 상태 변화를 검사하는 것이다.

주의해야 할 점은, 상태 변화를 검사하는 대상은 영속 상태여야 한다.
비영속, 준영속은 dirty checking의 대상이 되지 못한다.

기본적으로 더티체킹에 의해 생성되는 update Query는 모든 필드를 업데이트 하는데, 변경된 필드만 업데이트 하고 싶을 경우에는 아래와 같이 엔티티 클래스에 DynamicUpdate 어노테이션을 붙여주면 된다.

@Entity
@Getter
@Setter
@NoArgsConstructor
@DynamicUpdate
@Table(name = "customers")
public class Customer {
    @Id
    private long id;
    private String firstName;
    private String lastName;
.
.
.
EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();

Customer customer = new Customer();
customer.setId(1L);
customer.setFirstName("FirstName");
customer.setLastName("LastName");

em.persist(customer);
transaction.commit();

transaction.begin();

Customer entity = em.find(Customer.class, 1L);
em.remove(entity);

transaction.commit(); // flush -> DELETE ..

entityManager의 remove()메소드를 호출하여 영속성 컨텍스트에서 제거한 이후, 쓰기 지연 저장소에 DELETE문을 저장. → 이후 커밋 시 해당 쿼리 수행된다.

영속성 컨텍스트를 사용함으로써 얻을 수 있는 이점

  • 1차 캐시 (조회 성능)
  • 동일성 보장 (1차캐시를 이용하여 조회하기 때문에, 같은 식별자(Id)에 대해 매번 같은 인스턴스 접근 가능.
    하지만 동일성 보장 때문에 문제가 생길 수 있는데, 나중에 포스팅 예정.)
  • 트랜잭션을 지원하는 쓰기 지연 (트랜잭션을 커밋해서 영속성 컨텍스트를 플러시하기 전까지는 데이터베이스에 데이터를 등록, 수정, 삭제 하지 않는다. 따라서 커밋 전까지 데이터베이스 로우에 락을 걸지 않아서 락이 걸리는 시간이 최소화 된다.)
  • 변경 감지
  • 지연 로딩 (나중에 상세 포스팅 예정.)
profile
우상향 하는 개발자

0개의 댓글