JPA로 개발하며 느낀 한계
기존에 JPA를 활용하여 개발하면서 영속성 컨텍스트와 EntityManager에 대해서 알고는 있었다.
하지만, 솔직히 자세히 알지는 못했기에 확신을 갖고 개발하지는 못했던 것 같다.
따라서 이번에는 영속성 컨텍스트에 대해서 알아보고, 이 영속성 컨텍스트의 역할에 대해서 알아볼 예정이다.
영속성 컨텍스트
영속성 컨텍스트 개념 + 간단한 EntityManger 개념
영속성 컨텍스트는 엔티티를 관리하는 1차 캐시이다.
영속성 컨텍스트는 특정 저장소에서 관리하는 엔티티들의 집합을 의미하고 엔티티의 생성, 영속화, 제거 범위를 결정한다.
따라서 DB에서 가져온 엔티티를 보관하고, 변경 사항을 추적하는 역할을 수행한다.
EntityManager는 이러한 영속성 컨텍스트와 통신하고 엔티티 상태를 관리하는데 사용한다. 또한, flush를 통해 변경된 내용을 DB에 반영시킬 수 있다.
영속성 컨텍스트의 엔티티 관리
영속성 컨텍스트는 엔티티를 관리하는 역할을 한다.
이러한 엔티티는 영속성 컨텍스트 내부에서 영속성 ID를 통해 영구적으로 식별된다.

영속성 컨텍스트는 영속성 ID와 현재 Entity 상태뿐만 아니라 스냅샷을 저장한다.
여기서 스냅샷은 엔티티가 영속성 컨텍스트에 들어왔을 때의 최초 상태를 저장하는데 사용되고, 추후 변경 감지에서 유용하게 사용된다.
MySQL 기준 primary key가 영속성 ID의 역할을 수행한다.
영속성 컨텍스트 1차 캐싱
영속성 컨텍스트의 1차 캐싱에 대해서 알아보자.
엔티티들을 관리하는 EntityManager는 Thread-safe하지 않기에, 각 쓰레드마다 별도의 EntityManager와 영속성 컨텍스트를 갖는다.
이 때, 영속성 컨텍스트는 DB에서 조회한 데이터를 저장하는 1차 캐시로서 아래와 같이 동작한다.

- 1차 캐시에서 데이터 조회
- 1차 캐시에 데이터가 없는 경우, DB에서 데이터를 조회 (2차 캐시를 추가로 조회할 수도 있음)
- DB에서 조회한 데이터를 1차 캐시에 저장
이러한 영속성 컨텍스트의 1차 캐시는 DB 접근 횟수를 줄이고, 그렇기에 성능 향상을 돕는다.
영속성 컨텍스트의 변경 감지 ( Dirty Checking )
EntityManager의 flush 실행시, 영속성 컨텍스트는 엔티티를 스냅샷과 비교한다.
만약 변경 내용이 있으면 이를 update 쿼리로 만들어서 지연 sql 저장소에 저장한다.
지연 sql 저장소의 쿼리들은 일괄적으로 DB에 전달된다.
즉, 정리하자면 아래와 같이 동작한다.
- 데이터가 변경되면, 1차 캐시에 반영 후 지연 sql 저장소에 변경 사항에 대한 쿼리 저장
- 트랜잭션 종료(commit) 후, 지연 sql 저장소의 쿼리문을 모아서 DB에 전송한다.
영속성 컨텍스트의 변경 감지는 트랜잭션 종료 후 최종 변경 사항만 종합하여 DB에 반영하는 개념이다.
이는 불필요한 갱신을 줄이기 때문에 성능 향상을 돕는다.
영속성 컨텍스트와 2차 캐시를 활용한 조회 성능 최적화
영속성 컨텍스트와 2차 캐시를 활용하면, 성능 최적화 및 동시성 극대화의 효과를 얻을 수 있다.
2차 캐시는 애플리케이션 자체 캐시이고, Hibernate 등의 JPA 구현체는 이를 지원한다.
우선 2차 캐시를 활용한 영속성 컨텍스트의 DB 조회 흐름을 확인해보자.

- 1차 캐시에서 데이터 조회
- 1차 캐시에 데이터가 없으면 2차 캐시에서 데이터 조회
- 2차 캐시에 데이터가 없으면 DB에서 데이터 조회
- DB의 데이터를 2차 캐시에 저장
- 2차 캐시의 데이터 복사본을 1차 캐시에 저장
- 1차 캐시의 데이터를 사용자에게 전달
여기서 주의깊게 확인할 부분은 2차 캐시의 복사본을 1차 캐시에 전달한다는 것이다.
EntityManager는 thread-unsafe이므로 각 쓰레드는 독립적인 EntityManager와 영속성 컨텍스트를 갖는다.
여기서 2차 캐시가 복사본을 반환함으로써 아래 효과를 얻는다.
- 동시성 문제 최소화 : 각 쓰레드는 데이터의 원본이 아닌 복사본을 수정하므로, 추후 데이터를 병합하는 과정에서 동시성 문제를 최소화하고 데이터 일관성을 챙길 수 있다.
- 락 최소화 : 동시성 문제 해결을 위해 락을 사용하는 것보다는 데이터를 복사하는 것이 성능 측면에서 더 좋다.
또한, 2차 캐시는 애플리케이션 종료 전까지 유지되고 모든 쓰레드가 공유하는 캐시 저장소이다.
따라서 트랜잭션 단위로만 유지되는 1차 캐시만 사용하는 방식에 비해서 조회 성능을 효과적으로 향상시킬 수 있다.
EntityManager
EntityManager 개념
EntityManager는 영속성 컨텍스트에 접근하기 위한 도구이다.
우리가 DB에 접근하려고 하면, EntityManager가 영속성 컨텍스트에게 해당 요청을 전달하고 영속성 컨텍스트는 1차 캐시 혹은 DB의 값을 반환하는 개념이다.
EntityManager는 아래와 같은 역할을 담당한다.
- 영속성 엔티티 생성 및 제거 (persist, remove)
- 기본키 기반 엔티티 조회 (find)
- 엔티티 대상으로 쿼리 실행 (createQuery)
Container managed EntityManager
컨테이너 관리 EntityManager는 Java EE가 관리하는 EntityManager로, 기본값으로 사용된다.
해당 EntityManager는 트랜잭션이 진행되는동안 하나의 영속성 컨텍스트를 공유한다. (영속성 컨텍스트 전파)
기본적으로 EntityManager는 thread unsafe하므로 각 쓰레드마다 EntityManager를 생성 및 관리해야한다.
컨테이너 관리 EntityManager에서는 EntityManager의 관리를 컨테이너가 담당하므로, 개발자가 EntityManager 생성 및 제거 책임을 갖지 않는다.
컨테이너 관리 EntityManager에서는 아래와 같이 컨테이너를 통해 주입받을 수 있다.
@PersistenceContext
EntityManager em;
Application managed EntityManager
이번에는 애플리케이션 관리 EntityManager에 대해서 알아보자.
애플리케이션 관리 EntityManager는 개발자가 직접 EntityManager의 생명주기를 관리해야 한다.
애플리케이션 관리 EntityManager는 EntityManagerFactory를 통해 직접 EntityManager를 생성한다.
트랜잭션도 사용자가 직접 UserTransaction을 활용하여 관리하는 방식이다.
class ApplicationManagedEntityManager{
@PersistenceUnit
EntityManagerFactory emf;
@Resource
UserTranscation ut;
void processTranscation(){
EntityManager em = emf.createEntityManager();
try {
// 트랜잭션 시작
ut.begin();
// em으로 엔티티 관리
// 트랜잭션 커밋
ut.commit();
}catch(Exception e){
// 트랜잭션 롤백
ut.rollback();
}
}
}
이러한 애플리케이션 관리 EntityManager는 독립적인 트랜잭션이 필요한 경우에 유용하게 사용될 수 있다.
Entity 생명주기
엔티티는 EntityManager가 관리하는 객체로, EntityManager는 아래 4가지 상태를 통해 엔티티를 관리한다.
- 비영속 (new)
- 영속 (managed)
- 준영속 (detached)
- 삭제 (removed)
각 상태에 대해서 보다 자세하게 알아보자.
비영속 (new)
비영속 상태의 엔티티는 영속성 ID가 없고, 영속성 컨텍스트에 의해 관리되지 않는다.
@PersistenceContext
EntityManager em;
void EntityLifeCycle(){
User user = new User(); // new
}
즉, EntityManager가 관리하지 않고 DB에도 저장되지 않은 상태라고 볼 수 있다.
영속 (Managed)
영속 상태의 엔티티는 영속성 ID가 있고, 영속성 컨텍스트에 의해 관리된다.
엔티티가 영속 상태로 만들기 위해 아래와 같이 persist 메서드를 호출할 수 있다.
@PersistenceContext
EntityManager em;
void EntityLifeCycle(){
User user = new User(); // state : new
em.persist(user); // state : managed
}
persist를 통해 엔티티를 영속 상태로 만들면, 엔티티는 영속성 컨텍스트에 저장되지만 DB에 저장되지는 않는다.
DB에 저장되는 것은, EntityManager의 flush 메서드를 호출한 이후이다.
준영속 (detached)
준영속 상태의 엔티티는 영속성 ID가 있지만, 영속성 컨텍스트에 의해서 관리되지는 않는다.
영속 상태의 엔티티를 detach를 통해 준영속 상태로 만들 수 있고, 다시 merge를 통해 영속 상태로 만들 수 있다.
@PersistenceContext
EntityManager em;
void EntityLifeCycle(){
User user = new User(); // state : new
em.persist(user); // state : managed
em.detach(user); // state : detached
em.merge(user); // state : managed
}
삭제 (removed)
삭제 상태의 엔티티는 영속성 ID가 있고, 영속성 컨텍스트에서 관리한다.
영속 상태와의 주요 차이점은 트랜잭션 커밋 후 삭제가 예정된 상태라는 점이다.
remove 메서드를 사용하면, 영속 상태의 엔티티를 삭제 상태로 만들 수 있다.
@PersistenceContext
EntityManager em;
void EntityLifeCycle(){
User user = new User(); // state : new
em.persist(user); // state : managed
em.detach(user); // state : detached
em.merge(user); // state : managed
em.remove(user); // state : removed
}
마무리
이번에는 영속성 컨텍스트와 EntityManager에 대해서 알아보았다.
또한, EntityManager가 각 Entity의 상태를 어떻게 관리하는지에 대해서도 알아보았다.
다음에는 JPA Repository 사용법에 대해서 자세히 알아볼 예정이다.
'Spring > 기초 개념' 카테고리의 다른 글
| [Spring Boot] Spring MVC + WebClient 환경에서 JPA 사용하기 (0) | 2025.04.09 |
|---|---|
| [Spring Boot] Spring AOP Self Invocation과 @Transactional (0) | 2025.04.07 |
| [Spring Boot] 실시간 비동기 작업 처리기 만들기 - 실습 (0) | 2025.03.26 |
| [Spring Boot] 실시간 비동기 작업 처리기 만들기 - 기초 개념 (0) | 2025.03.19 |
| SpringBoot에서 Mockito로 소셜 로그인 단위 테스트 (0) | 2025.02.14 |