스프링에서 JPA
를 다루다보면 EntityManager
객체를 만나게 된다.EntityManager
는 JPA
인터페이스의 일부로, 자바 객체(@Entity
)를 DB에 저장된 데이터와 맵핑해주는 ORM 기술을 정의한 인터페이스이다.
@Repository
public class JpaRepository {
@PersistenceContext
private EntityManager em; // 바로 요놈!
}
이번 글에서는 EntityManager
와 Persistence Context
에 대해 자세히 알아보도록 하자.
EntityManager
는 이름 그대로, @Entity
어노테이션을 달고 있는 Entity
객체들을 관리하며 실제 DB 테이블과 매핑하여 데이터를 조회/수정/저장 하는 중요한 기능을 수행한다. EntityManager
는 PersistenceContext
라는 논리적 영역을 두어, 내부적으로 Entity
의 생애주기를 관리한다.
Entity
객체는 크게 다음의 네 가지 상태 중 하나의 상태를 갖는다.
처음 생성되어 아직 EntityManger
의 관리를 받지 않는 상태를 의미한다. 이 상태에 있는 Entity
는 순수한 자바 객체라고 볼 수 있다.
EntityManager
에 의해 관리되고 있는 단계이다. EntityManager
에서 persist()
메서드를 호출하거나 JPQL
로 쿼리된 Entity
객체는 이 상태에 놓이게 된다. managed
상태는 Entitymanager
내부의 Persistence Context
에 데이터가 저장된 일종의 캐싱 상태에 가까우며, DB에 실제 데이터가 저장된 것을 의미하는 것은 아니다. 이는 EntityManager
의 특징인 쓰기지연(Transactional Write-Behind
)과 관련이 있다.
EntityManager
가 더 이상 관리하지 않는 상태로, managed
상태에서 EntityManager
로 부터 분리된 상태이다
EntityManager
의 Persistence Context
에서 삭제된 상태이다. Detached
상태와 달리 Removed
상태가 되면, DB에 맵핑된 데이터도 함께 제거된다.
@Repository
public class JpaRepository {
@PersistenceContext
private EntityManager em; // 바로 요놈!
public Long save(Member member) { // 1. new 상태
em.persist(member); // 2. managed 상태
em.detach(member); // 3. detached 상태
return member.getId();
}
}
EntityManager
는 데이터베이스와 어플리케이션 사이에 위치하여, 편리한 기능들을 제공한다. 대표적인 특징으로는 First Level Cache
, Identity
, Write-Behind
, Dirty Check
, Lazy Loading
등이 있다.
EntityManager
는 Persistence Context
에 등록된 Entity
객체의 @Id
어노테이션이 붙은 필드 값을 사용하여 Map<Id, Entity>
형태로 저장한다. 어플리케이션에서 데이터 조회를 요청하면, EntityManager
는 먼저 내부에 캐싱된 Entity
가 있는지 확인한다. 데이터가 존재하면 DB에 쿼리를 전송하지 않고, 캐싱된 Entity
를 반환하기 때문에 쿼리 성능이 최적화된다.
앞서 설명한 바와 같이, EntityManager
는 내부적으로 Entity
를 저장한다. 따라서, 동일한 Entity
를 요청할 때에는 동일한 Entity
를 반환함을 보장한다.
EntityManager
는 실제 트랜젝션이 커밋되기 전까지 발생한 쿼리를 모아서, commit()
이 호출될 때 한번에 전달한다. 이러한 방식은 데이터를 처리하는 과정에서 의도치 않은 에러가 발생할 경우 roll-back이 용이하며, 네트워크 비용을 최소화할 수 있다는 장점을 지닌다.
EntityManager
에 등록된 Entity
에 수정이 발생하면, 트랜잭션이 커밋되는 시점에 First Level Cache
와의 비교를 통해 변경사항을 감지하여 UPDATE
쿼리를 자동으로 실행한다.
@ManyToOne
과 같이 다대일 관계를 가진 Entity
를 조회할 때, 실제로 해당 Entity
의 데이터에 직접 접근하는 시점에 쿼리를 실행하는 방식을 의미한다. Lazy Loading
을 통해 EntityManager
는 불필요한 쿼리를 최소화한다.
EntityManagerFactory
는 EntityManager
를 생성하는 클래스이다. EntityManager
의 생성방식은 크게 Container-Manager
방식과 Application-Managed
구분된다.
스프링 컨테이너에 등록된 EntityManagerFactory
에서 EntityManager
를 생성하여 주입하는 방식을 의미한다.
@PersistenceContext
EntityManager entityManager;
또한, Container-Managed
방식은 Thread-Safe
하다는 특징이 있다. 스프링 컨테이너는 EntityManager
를 주입할 때, 실제로는 이를 감싼 프록시 객체를 주입한다. 프록시 객체는 내부적으로 Thread-Lock
과 관련된 기능을 구현하고 있어 멀티 스레딩 환경에서 안전하게 사용할 수 있다.
스프링 컨테이너에 등록된 EntityManagerFactory
를 주입받아, 어플리케이션에서 직접 EntityManager
를 생성하여 사용하는 방식을 의미한다. Container-Managed
방식 보다 EntityManager
를 유연하게 관리할 수 있다는 것이 특징이다.
@Repository
public class JpaRepository {
@AutoWired
private EntityManagerFactory emf;
public Long save(Member member) { // 1. new 상태
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
...
do somegthing
em.persist(member)
...
tx.commit();
}
catch(Exception e) {
tx.rollback();
} finally {
em.close();
}
}
}