일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- lombok
- Volatile
- nestjs
- 일급 컬렉션
- 일급 객체
- java
- factory
- synchronized
- spring security
- builder
- middleware
- Spring
- OAuth 2.0
- Google OAuth
- Dependency Injection
- Today
- Total
HJW's IT Blog
JPA - EntityManager와 영속성 컨텍스트 이해하기 본문
0. 들어가며
JPA(Java Persistence API)는 Java 애플리케이션에서 데이터베이스와의 상호작용을 단순화하는 ORM(Object-Relational Mapping) 기술이다. 그중 EntityManager는 엔티티를 관리하고 데이터베이스의 CRUD 작업을 수행하는 핵심 객체이다. 이 글에서는 EntityManager의 역할과 특징, 그리고 영속성 컨텍스트의 동작 방식에 대해 다뤄보겠다.
1. EntityManager 란?
JPA 에서 Entity 를 관리하는 객체로, 데이터베이스의 CRUD 작업을 수행하는 역할을 한다. 다음과 같은 주요 기능을 수행한다.
1.1 핵심 기능
EntityManager 는 Entity 의 생명 주기를 관리한다.
- 영속화 (Persist) : 엔티티 객체를 DB에 저장
- 조회 (Find) : 데이터베이스에서 특정 엔티티를 조회
- 수정(Merge) : 엔티티 객체의 변경사항을 DB에 반영
- 삭제 (Remove) : DB에서 엔티티 삭제
- 분리 (Detach) : 엔티티 객체를 EntityManager의 관리 대상에서 제외한다.
- 병합 (Merge ) : 분리된 엔티티 객체의 상태를 영속성 컨텍스트에 반영한다.
- 비우기 (Clear ) : 영속성 컨텍스트 초기화
EntityManager 의 주요 특징
- 인터페이스 : 실제 구현은 JPA 구현체에 의해 제공된다 (ex. hibernate)
- Not Thread-Safe : 각 쓰레드, 요청마다 별도의 EntityManager 를 사용해야 안전하다
- 영속성 컨텍스트 : EntityManager는 영속성 컨텍스트를 통해 엔티티를 관리한다
1.2 EntityManagerFactory 란?
EntityManager 객체를 생성하는 Factory 역할을 한다. DB와의 연결을 유지하며 여러 EntityManager 인스턴스를 생성할 수 있다.
주요 특징
- 어플리케이션 실행시, 한번만 생성후, 이후 공유 자원으로써 사용된다
- Thread - Safe 하다
- 비용이 크기 때문에, 어플리케이션 종료시 close 해주어야 한다
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistenceUnit");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin(); // 트랜잭션 시작
// 엔터티 저장
User user = new User();
user.setName("John Doe");
em.persist(user);
em.getTransaction().commit(); // 트랜잭션 커밋
em.close(); // EntityManager 종료
emf.close(); // EntityManagerFactory 종료
2. 영속성 컨텍스트 (Persistence Context)
영속성 컨텍스트는 JPA
에서 엔티티를 효율적으로 관리하기 위해 제공하는 핵심적 개념이다. 즉, 엔티티 객체드를 관리하는 논리적인 캐시 또는 작업 공간이다.
2.1 핵심 역할
- 1차 캐시
- 영속성 컨텍스트는 엔티티 객체를 메모리에 캐싱한다
- 같은 Transaction 내에서 동일한 엔티티를 여러 번 조회할 때, DB에 직접 접근하는 것이 아닌 캐시에서 가져온다
- 변경 감지 (Dirty Checking)
- 영속성 컨텍스트는 엔티티의 변경 사항을 추적한다
- Transaction Commit 시점에 변경된 엔티티만 DB에 업데이트 한다
- 쓰기 지연 (Write Behind)
- 엔티티를 DB에 즉시 반영하는 것이 아닌, Transaction 커밋 시점까지 영속성 컨텍스트에 보관한다
- Transaction Commit 시점에 한번에 여러 쿼리를 실행하여 DB 부하를 줄인다
- 동일성 보장 (Identity Management)
- 영속성 컨텍스트 내에서는 같은 엔티티를 나타내는 객체가 항상 동일함을 보장한다.
- 트렌젝션 지원 (Transaction support )
- 영속성 컨텍스트는 Transaction 범위 내에서 동작하며, Transaction 의 ACID 속성을 보장한다
- 영속 상태 관리
- 엔티티 생명 주기 를 관리하고 각 상태에 따른 적절한 동작을 수행한다.
2.2 영속성 컨텍스트의 생명 주기
- EntityManager 생성: Entity Manager를 생성하면 영속성 컨텍스트가 함께 생성된다.
- 트랜잭션 시작: 트랜잭션을 시작하면 영속성 컨텍스트가 활성화된다.
- 엔티티 관리: EntityManager를 통해 엔티티를 영속성 컨텍스트에 저장, 조회, 수정, 삭제한다.
- 트랜잭션 커밋/롤백:
- 커밋: 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하고 트랜잭션을 종료
- 롤백: 영속성 컨텍스트의 변경 내용을 무효화하고 트랜잭션을 종료
- EntityManager 종료: EntityManager를 종료하면 영속성 컨텍스트도 함께 종료
2.3 영속성 컨텍스트의 범위
웹 요청에선 Request 하나당 영속성 컨텍스트 하나를 사용하는것이 일반적이다. 영속성 컨텍스트를 어디까지 유지할 지에 따라 전략 (OSIV 등 ) 이 달리질 수 있다.
- OSIV : 일반적으로 JPA에선 트렌젝션이 종료되면, 영속성 컨텍스트도 종료된다.
- 하지만, OSIV가 활성화되면, 트렌젝션이 끝나도 영속성 컨텍스트가 유지된다.
- 따라서, Lazy Loading 등이 controller 나 view 에서 가능해진다.
- DB Connection 부족 문제, 대량의 Lazy loading 시 성능 저하
- 하지만 OSIV가 활성화되면 트랜잭션이 종료된 후에도 DB 커넥션이 유지될 수 있어 성능에 악영향을 미칠 수 있다.
예를 들어
@RestController
public class OrderController {
@PersistenceContext
private EntityManager em;
@GetMapping("/orders")
public List<Order> getOrders() {
return em.createQuery("SELECT o FROM Order o", Order.class).getResultList();
}
}
와 같은 상황에서, Order
엔티티가, User
, Product
등을 Lazy Loading 으로 참조하고 있다면? -> 각각 추가적인 쿼리가 실행된다. OSIV가 활성화된 상태에서는 Lazy Loading이 가능하지만, 트랜잭션이 종료된 후 추가적인 쿼리가 실행될 수 있어 주의가 필요하다.
4. 엔티티의 생명 주기
4.1 비영속 상태 (New / Transient)
이 시점의 객체는 JPA 의 영속성 컨텍스트에 의해 관리되지 않는 상태를 의미한다.
즉 EntityManager 와 연결되지 않은 상태로, 아직 DB와 아무 관계가 없는 상태이다.
특징
- new 키워드로 생성했지만,
persist()
를 호출하지 않은 상태 - Transaction 과도 무관하며, 언제든지 GC 의 대상이 될 수 있다.
4.2 영속 상태 (Managed / Persistent)
영속 상태란 엔티티가 영속성 컨텍스트에 의해 관리되는 상태를 의미한다. 이 상태에선, 1차 캐시, 변경 감지, 쓰기 지연, 지연 로딩 등 다양한 기능이 동작한다. 즉, Entity Manager 가 해당 객체를 추적 하는 상태이며, Transaction 이 종료될때, 변경 사항을 자동으로 반영한다.
✔ JPA가 엔티티를 관리하는 상태
✔ persist()
, find()
, merge()
를 통해 영속성 컨텍스트에 등록 할 수 있으며, 영속 상태가 된다
✔ Transaction Commit -> Dirty Checking 을 통한 DB 반영
✔ remove()
, detach()
를 호출하기 전까지 엔티티는 관리된다.
// 1. 비영속 상태
User user = new User();
user.setName("John Doe"); // 아직 DB와 관계없음 (비영속 상태)
// 2. persist()를 호출하면 영속 상태로 변경됨
em.persist(user);
System.out.println(em.contains(user)); // true (영속 상태)
4.2.1 영속상태의 주요 특징
✅ 1차 캐시
- 엔티티를 조회할때, 우선 영속성 컨텍스트 에서 찾는다.
- 동일한 Transaction내에서 같은 엔티티를 다시 조회해도 DB에 접근하는 것이 아닌 영속성 컨텍스트에서 찾는다.
✅ 변경 감지 (Dirty Checking)
- 영속 상태의 엔티티 값이 변경되면, 자동으로 UPDATE SQL 이 실행된다.
User user = em.find(User.class, 1L); // 영속 상태 user.setName("Updated Name"); // 값 변경 em.getTransaction().commit(); // 변경 감지 → UPDATE 실행
- 즉, em.update() 와 같은 메서드가 필요하지 않다.
✅ 지연 로딩 (Lazy Loading)
- `fetch = FetchType.LAZY` 로 설정하여 연관된 엔티티는 실제 사용할 때 까지 쿼리를 실행하지 않는다. 필요한 시점에 **Proxy객체** 를 통해 DB 에서 데이터를 가져온다
User user = em.find(User.class, 1L); // User 엔티티 조회 (DB 접근)
System.out.println(user.getOrders()); // 이 시점에서 ORDER 테이블 조회
✅ 쓰기 지연 (Write - Behind)
em.persist()
호출시 ,즉시 DB 엔 반영되는 것이 아닌, commit 시점에 INSERT 문이 실행된다.EntityTransaction tx = em.getTransaction(); tx.begin();
User user1 = new User("Alice");
User user2 = new User("Bob");
em.persist(user1);
em.persist(user2);
// 아직 INSERT SQL 실행 X (쓰기 지연 저장소에 쌓임)
tx.commit(); // 여기서 한 번에 INSERT 실행 🚀
✅ Flush
- Flush 와 Commit 의 차이
- Flush : Transaction 유지, DB 반영
- Commit : DB 반영, Transaction 종료
- 영속성 컨텍스트의 내용을 DB에 반영하는 작업
- Transaction이 커밋되지 않아도 특정 시점에서 자동 실행될 수 있음
- commit 호출시
- JPQL 실행 시점
- flush 강제 호출시
- `flush()` 이후 `rollback()` 호출시 변경사항 취소
- Flush 시점 설정하기
- AUTO : 기본값
- COMMIT : 기존에 JPQL 실행시 자동으로 되던 FLUSH 를 방지한다
### 4.3 준영속 상태 (Detached)
이 상태의 객체는 JPA 의 영속성 컨텍스트에서 분리되어 있는 상태를 의미한다.
즉 더이상 Dirty Checking, 1차 캐시, Lazy Loading 등의 기능이 동작하지 않는다
`detach()` : 특정 엔티티를 준영속 상태로 만든다
`clear()` : 영속성 컨텍스트 초기화, 모든 엔티티가 준영속 상태가 된다
`close()` : 영속성 컨텍스트 종료, 모든 엔티티가 준영속 상태가 된다.
`merge()` : 준영속 상태의 객체를 다시 영속 상태로 만든다.
**주의** : merge 는 준영속 상태의 엔티티를 받아 새로운 영속 상태의 엔티티를 반환하는 것이다. 즉, merge 이후엔, 반환된 영속 엔티티를 사용해야 한다.
```java
User detachedUser = em.find(User.class, 1L); // 영속 상태
em.detach(detachedUser); // 준영속 상태
User mergedUser = em.merge(detachedUser); // 새로운 영속 상태 객체 반환
System.out.println(em.contains(detachedUser)); // false (여전히 준영속)
System.out.println(em.contains(mergedUser)); // true (새로운 영속 객체)
4.4 삭제 상태 (Removed)
이 상태의 객체는 더이상 영속성 컨텍스트에서 관리되지 않으며, Transaction commit 시, 실제로 DB 에서 삭제 되는 상태가 된다.
remove(entity)
를 호출하면, 엔티티가 removed 상태가 된다.
Transaction commit 시, 실제 DELETE SQL 이 실행된다.
1차 캐시에서도 삭제된다.
'JAVA' 카테고리의 다른 글
JPA 연관관계에서 프록시 객체의 역할과 한계 (0) | 2025.02.26 |
---|---|
JPA - Entity들 사이의 연관관계 (0) | 2025.02.25 |
DTO ↔ 엔티티 변환, MapStruct로 자동화하기 (0) | 2025.02.18 |
Spring 없이 의존성 관리와 팩토리 패턴 구현하기 (0) | 2025.01.24 |
JVM 은 어떻게 동작하는가 (1) | 2025.01.24 |