JPA - 영속성 관리(1)

본 글은, 자바 ORM 표준 JPA 프로그래밍(김영한님) 책을 요약 정리한 포스팅입니다.

실무에서 필요한JPA - 영속성 관리(1)

학습목표

  • EntityMangerFacotry와 EntityManager의 관계를 이해한다.
  • PersistenceContext(영속성 컨텍스트)의 특징을 이해하고, CRUD에서 어떻게 동작하는지를 이해한다.
  • 영속성 컨텍스트가 관리하는 엔티티의 생명주기를 이해한다.
  • Flush에 대해서 알아본다.
  • 준영속 상태의 특징을 이해한다.

엔티티 매니저 팩토리와 엔티티 매니저

  • EntityManagerFactory는 EntityManager를 만드는 공장인데, 공장을 만드는 비용은 상당히 크다. 따라서 싱글턴으로 만들어서 웹 애플리케이션에서 공유하도록 설계되어있다.
  • EntityMangaer는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않는다. (Lazy). 트랜잭션을 시작할때 커넥션을 획득한다.
  • Hibernate를 포함한 JPA 구현체들은 EntityManagerFactory를 생성할 때 커넥션 풀도 만든다.

영속성 컨텍스트(Persistence Context)

  • JPA를 이해하는데 가장 중요한 핵심
  • 엔티티를 영구히 저장하는 환경정도라고 이해하면 된다.
  • EntityManager를 생성할때 영속성 컨텍스트가 만들어진다.
  • EntityManger를 통해서 영속성 컨텍스트에 접근하거나 관리할 수 있다.

엔티티 생명주기

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

출처: 자바 JPA 프로그래밍(김영한)

영속성 컨텍스트의 특징

  • 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 매핑한 값) 으로 구분한다. 영속상태는 식별자 값이 반드시 있어야 한다. 없으면 예외가 발생한다.
  • 영속성 컨텍스트와 데이터 베이스 저장. JPA는 보통 트랜잭션을 커밋하는 순간, 영속성컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영한다(Flush)
  • 영속성 컨텍스트의 장점
    • 1차 캐시
    • 동일성 보장
    • 트랙잭션을 지원하는 쓰기 지연
    • 변경 감지
    • 지연 로딩

엔티티 조회

  • em.find(Member.class, “member1”)를 호출하고, 1차 캐시를 먼저 찾는다.
  • 없으면 데이터베이스에서 조회하고, 조회한 데이터를 1차캐시에 저장한다.
  • 이후에 member1로 조회하게되면 영속성 컨텍스트에서 식별자를 통해서 해당 엔티티를 반환한다. (캐쉬가 동작하는 원리)

엔티티 등록

  • em.persist(memberA) 까지는 INSERT SQL문을 데이터베이스에 보내지 않는다. transaciton.commit() 커밋하는 순간에 보낸다. → 성능최적화에서 다룬다.✅
    • EntityManager는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 그리고 트랜잭션을 커밋할때 모아둔 쿼리를 데이터베이스에 보내는데, 이를 트랜잭션을 지원하는 쓰기 지연 이라 한다.
  • 트랜잭션을 커밋하면 EntityManger는 우선 영속성 컨텍스트를 Flush한다. Flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 하는 작업이다.

엔티티 수정(변경 감지)

  • JPA로 엔티티를 수정할 때는 엔티티를 조회해서, 데이터만 변경하면 된다.이렇게 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능을 변경감지(Dirty checking)
  • JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는데 이것을 스냅샷이라고 한다. Flush 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾는다.
  • 변경감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다.
  • JPA의 기본전략은 엔티티의 모든 필드를 업데이트 한다.
    • 모든 필드를 사용하면, 데이터 전송량이 증가하는 단점이 있지만 다음과 같은 장점으로 인해 모든 필드를 업데이트 한다.
      • 모든 필드를 사용하면 수정 쿼리는 항상 같다. 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용 가능하다.
      • 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리를 재사용 할 수 있다.
  • 만약에, 필드가 너무 많거나 저장되는 내용이 너무 크면 수정된 데이터만 사용해서 동적으로 UPDATE SQL를 생성하는 전략을 선택하면 된다. 이때는 하이버네이트 확장 기능을 사용해야 한다.
@Entity
@org.hibernate.annotations.DynamicUpdate
@Table
public class Member {...}

엔티티 삭제

  • 엔티티 등록과 비슷하게 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록한다. 이후 트랜잭션 커밋해서, 플러시를 호출하면 실제 데이터베이스에 삭제 쿼리를 전달한다.

플러시(Flush)

  • 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
  • 영속성 컨텍스트를 플러시 하는 방법
    • em.flush()를 직접 호출한다. (거의 사용하지 않는다.)
    • 트랜잭션 커밋시 플러시가 자동 호출된다. (JPA는 트랜잭션을 커밋할때)
    • JPQL쿼리 실행 시 플러시가 자동 호출된다.
  • 플러시 모드 옵션
    • javax.persistence.FlushModeType를 사용한다.
      • em.setFlusMode(FlushModeType.COMMIT) 직접 설정
      • FlushModeType.AUTO: 커밋이나 쿼리를 실행할때 플러시(기본값)
      • FlushModeType.COMMIT: 커밋할 때만 플러시

준영속

  • 영속성 컨텍스트가 관리하는 영속상태의 엔티티가 영속성 컨텍스트에서 분리된 것을 준영속 상태라 한다. 준영속 상태 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다.
  • 준영속 상태로 만드는 방법
    • em.detach(entity): 특정 엔티티만 준영속 상태로 전환한다.
    • em.clear(): 영속성 컨텍스트를 완전히 초기화한다.
    • em.close(): 영속성 컨텍스트를 종료한다.
      • 1차캐시, 쓰지지연 저장소등이 존재하지 않는다. 종료되었다.
  • 준영속 상태의 특징
    • 거의 비영속 상태에 가깝다. 영속성 컨텍스트가 제공하는 1차 캐시, 쓰기 지연, 변경감지, 지연로딩 어떠한 기능도 동작하지 않음
    • 식별자 값을 가지고 있다.
    • 지연로딩을 할 수 없다.
  • 병합: merge()
    • 준영속 상태의 엔티티를 다시 영속 상태로 변경하려면 병합을 사용하면 된다.

정리

  • 엔티티 매니저는 엔티티 매니저 팩토리에서 생성한다. J2SE환경에서는 엔티티 매니저를 만들면 그 내부에서 영속성 컨텍스트도 함께 만들어진다.
  • 영속성 컨텍스는 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 역할을 한다. 덕분에 1차캐시, 동일성 보장 트랜잭션을 지원하는 쓰기지연, 변경 감지 , 지연로딩 기능을 사용할 수 있다.
  • 영속성 컨텍스트에 저장한 엔티티는 플러시 시점에 데이터베이스에 반영되는데, 일반적으로 트랜잭션을 커밋할 때 영속성 컨텍스트가 플러시 된다.
  • 영속성 컨텍스트의 관리를 받지 못하는 엔티티를 준영속 상태 라 하고, 이는 영속성 컨텍스트가 제공하는 1차 캐시, 동일성 보장, 쓰기지연, 변경감지, 지연로딩 같은 기능을 사용할 수 없다.