EntityManager 란?
JPA (Java Persistence API)에서 엔티티(Entity)를 관리하는 객체입니다.
즉, 데이터베이스와 애플리케이션 사이에서 엔티티의 생명주기를 관리하는 역할을 수행합니다.
영속성 컨텍스트란?
영속성 컨텍스트란 엔티티를 영구 저장하는 환경입니다. 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할을 합니다. 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리합니다.
영속성 컨텍스트의 구성 요소
1차 캐시 : 식별자 기준 엔티티 저장, 중복 쿼리 방지.
쓰기 지연 SQL 저장소 : 트랜잭션 커밋 시 DB 반영용 INSERT/UPDATE 모음
변경 감지 : Flush 시점에 변경된 필드 자동 감지.
지연 로딩 관리 : 프록시로 엔티티 참조를 나중에 불러옴 (필요한 시점에)
엔티티의 생명주기
1. 비영속 : 아직 영속성 컨텍스트에 등록되지 않은 상태 (DB와 전혀 관련 없음)
2. 영속 : 영속성 컨텍스트에 저장된 상태
3. 준영속 : 한때 영속 상태였지만 현재는 관리되지 않은 상태
4. 삭제 : 영속성 컨텍스트에서 삭제된 상태, 커밋 시 DB에서도 삭제됩니다.
public class Test {
@Test
void testEntityManagerFactory() {
EntityManager em = entityManagerFactory.createEntityManager();
User user = new User(); // 비영속
em.persist(user); // 영속
em.detach(user); // 준영속
em.clear(); // 모든 영속 -> 준영속
em.merge(user); // 준영속 -> 영속
em.remove(user); // 삭제.
}
}
영속성 컨텍스트는 EntityManager 마다 하나씩 만들어집니다. 따라서 위에서 서술한 영속성 컨텍스트의 기능은 하나의 엔티티매니저 내에서 유효한 기능입니다. 아래와 같은 차이점이 발생할 수 있습니다!
@Test
void testEntityManagerFactory() {
EntityManager em = entityManagerFactory.createEntityManager();
EntityManager em1 = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
User user = new User();
em.persist(user);
em.flush();
em.getTransaction().commit();
User u1 = em.find(User.class, user.getId());
System.out.println("---------------- em find After");
User u2 = em1.find(User.class, user.getId());
System.out.println("---------------- em1 find After");
}

위의 코드를 실행하였을 때 전송되는 쿼리입니다. 이를 보시면 User user는 em 이라는 엔티티 매니저에 영속된 상태로 DB에 Flush 되었기 때문에 em.find(User.class, user.getId()) 함수를 호출하여 u1 을 불러올 때 DB를 통해 조회하지 않고, 1차 캐시를 활용합니다.
그로 인해, 쿼리가 발생하지 않습니다.
그러나, u2를 불러올 때는 em1 이라는 엔티티 매니저를 통해 함수를 실행하였기 때문에 1차 캐시에 존재하지 않아 DB를 통해 데이터를 가져오게 됩니다.
System.out.println(u1 == user); // True
System.out.println(u2 == user); // False
System.out.println(u1 == u2); // False
u1.setNickname("닉네임 수정.");
System.out.println("u1 Nickname: " + u1.getNickname());
System.out.println("u2 Nickname: " + u2.getNickname());
System.out.println("user Nickname: " + user.getNickname());
user 객체는 em 의 1차 캐시에 저장되어 있기 때문에, u1 을 불러오는 시점에서는 1차 캐시에 존재한 user 객체를 그대로 가져오게 됩니다. 따라서 u1==user 가 True가 되는 것입니다. 이와 달리 u2는 DB에서 불러온 데이터로 새롭게 생성된 객체이기 때문에 False가 발생하게 됩니다. 또한 아래의 setter 를 통한 닉네임 수정의 결과가 u1,user 에 반영된 것을 보았을 때 간접 참조를 직접적으로 확인할 수 있습니다.
이를 통해 1차 캐시와 영속성 컨텍스트는 엔티티 매니저마다 별도로 적용된다는 것을 확인할 수 있습니다!
새로운 의문점
그렇다면, @Repository 를 붙여 사용하는 일반적인 Spring 의 레포지토리는 어떻게 엔티티 매니저를 관리하는 것일까?라는 의문점이 들게 됩니다. 왜냐하면 싱글톤을 통해 여러 요청들(트랜잭션)이 하나의 레포지토리의 메서드를 호출하게 되는데 그렇다면 동시성 문제가 발생할 수 있기 때문입니다.
예를 들어, 1차 캐시에 특정 요청이 등록하지 않은 데이터들이 캐시에 등록되는 문제와 같은 문제점이 발생할 수 있기 때문입니다.
프록시 객체를 통한 해결.
저와 비슷한 의문점을 가지신 분들이 올리신 질문들이 있었습니다.
해당 질문에서 영한님께서는 EntityManager는 실제 객체가 아닌 프록시 객체이고, 스레드(트랜잭션)마다 자신의 고유한 영속성 컨텍스트를 사용하게 된다는 것이었습니다.
즉, 다음과 같은 과정을 거치게 됩니다.
클라이언트의 요청(스레드) -> 트랜잭션 어노테이션이 붙은 메서드 또는 레포지토리의 함수 실행 -> 프록시 객체를 통해 해당 스레드 로컬에 EntityManager 할당
이와 같은 흐름을 아주 잘 정리해주신 분이 계셔서 링크를 함께 첨부해두겠습니다!
https://perfectacle.github.io/2021/05/24/entity-manager-lifecycle/
(JPA) 엔티티 매니저는 리퀘스트 당 하나만 생성되지 않을 수 있다.
3줄 요약 OSIV가 꺼져있으면 트랜잭션이 시작될 때 엔티티 매니저가 생성되고, 트랜잭션이 끝날 때 엔티티 매니저를 종료한다. OSIV가 꺼져있고, 다른 트랜잭션이라면 엔티티 매니저가 공유되지
perfectacle.github.io
'Back-End > Server' 카테고리의 다른 글
| [WAS] Java 로 순수하게 서버 구축하기 (1) (2) | 2025.06.10 |
|---|---|
| [팀 프로젝트] Enum 활용하기. (0) | 2025.04.22 |
| [팀 프로젝트] 유니크 제약 조건과 동시성 문제 (0) | 2025.02.01 |
| [팀 프로젝트] queryDsl 도입과 테스트 코드 (0) | 2025.01.21 |
| [Kong's Blog] 프로젝트 회고와 리팩토링 (4) - 스케쥴링 적용 (0) | 2025.01.17 |
댓글