Spring/Spring JPA
영속성 컨텍스트(Persistence Context)
개발자잡
2025. 1. 7. 22:26
JPA(Java Persistence API)의 핵심 개념 중 하나로, 엔티티(Entity) 객체를 관리하고 데이터베이스와의 상호작용을 책임지는 논리적 작업 공간입니다. 이를 통해 객체와 데이터베이스 간의 상태를 동기화하고, 애플리케이션 개발에서 생산성과 효율성을 높이기 위해 사용.
1. 영속성 컨텍스트(Persistence Context)란?
- 정의:
영속성 컨텍스트는 JPA가 제공하는 엔티티를 저장하거나 조회하는 작업을 담당하는 메모리 상의 캐시입니다.- 쉽게 말해, 엔티티 객체를 관리하는 1차 캐시 역할을 합니다.
- **엔티티 매니저(EntityManager)**를 통해 영속성 컨텍스트에 접근합니다.
- EntityManager = 영속성 컨텍스트를 관리하는 인터페이스.
2. 영속성 컨텍스트의 상태
엔티티 객체는 영속성 컨텍스트에서 다음과 같은 상태를 가질 수 있습니다.
1. 비영속(Transient)
- 영속성 컨텍스트와 전혀 관련 없는 상태.
- JPA에서 관리되지 않으며, 데이터베이스와 연관되지 않습니다.
- 예: new 키워드로 생성한 객체.
User user = new User(); // 비영속 상태
2. 영속(Managed)
- 영속성 컨텍스트에 저장되어 JPA가 관리하는 상태.
- 데이터베이스와 동기화되며, 변경사항은 영속성 컨텍스트에 반영됩니다.
User user = entityManager.find(User.class, 1L); // 영속 상태
3. 준영속(Detached)
- 영속성 컨텍스트가 더 이상 객체를 관리하지 않는 상태.
- 영속성 컨텍스트에서 분리되었으며, 데이터베이스와의 동기화가 중단됩니다.
entityManager.detach(user); // 준영속 상태
4. 삭제(Removed)
- 삭제 요청된 상태.
- 데이터베이스에서 해당 엔티티가 삭제되도록 예약된 상태입니다.
entityManager.remove(user); // 삭제 상태
3. 영속성 컨텍스트의 주요 동작 원리
3.1. 1차 캐시
- 영속성 컨텍스트는 엔티티 객체를 메모리에 캐싱하여, 반복적인 데이터베이스 조회를 방지합니다.
- 기본 동작:
- EntityManager.find()를 호출하면, 영속성 컨텍스트의 1차 캐시에서 엔티티를 먼저 조회합니다.
- 1차 캐시에 없으면 데이터베이스를 조회하고, 조회된 결과를 1차 캐시에 저장합니다.
User user1 = entityManager.find(User.class, 1L); // DB에서 조회 후 캐시에 저장
User user2 = entityManager.find(User.class, 1L); // 1차 캐시에서 조회
System.out.println(user1 == user2); // true (같은 객체 참조)
3.2. 동일성 보장
- 영속성 컨텍스트는 동일한 엔티티를 하나의 객체 인스턴스로 관리합니다.
- 같은 트랜잭션 내에서 동일한 ID를 가진 엔티티는 항상 같은 객체로 반환됩니다.
3.3. 변경 감지(Dirty Checking)
- 영속 상태의 엔티티가 변경되면, 영속성 컨텍스트는 이를 자동으로 감지하고 데이터베이스에 업데이트 쿼리를 전송합니다.
- JPA는 엔티티를 관리하면서, 스냅샷(초기 상태)을 유지하고 변경 여부를 비교합니다.
User user = entityManager.find(User.class, 1L); // 영속 상태
user.setName("New Name"); // 변경 감지
// 트랜잭션이 커밋되면 UPDATE 쿼리 전송
3.4. 지연 로딩(Lazy Loading)
- 영속성 컨텍스트는 엔티티와 연관된 데이터를 필요할 때만 로딩합니다.
- @OneToMany, @ManyToOne 등의 연관 관계 매핑에서 Lazy를 설정하면 연관 데이터를 바로 로딩하지 않고, 프록시 객체를 반환합니다.
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY) // Lazy 로딩 설정
private List<OrderItem> items = new ArrayList<>();
}
@Entity
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY) // Lazy 로딩 설정
private Order order;
private String productName;
}
// 사용 예제
Order order = entityManager.find(Order.class, 1L); // Order만 조회, OrderItem은 조회되지 않음
System.out.println(order.getItems().size()); // 이 시점에서 OrderItem 조회 쿼리 발생
3.5. 플러시(Flush)
- 플러시(Flush): 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 작업.
- 자동 플러시 시점:
- 트랜잭션이 커밋될 때.
- JPQL 실행 전에.
- EntityManager.flush()를 호출할 때.
// 플러시 동작 예제
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
User user = new User();
user.setName("John Doe");
// persist를 호출하면 영속성 컨텍스트에 저장되지만 DB에는 반영되지 않음
em.persist(user);
// flush를 명시적으로 호출하면 INSERT 쿼리가 실행됨
em.flush();
user.setName("Jane Doe"); // 변경 감지
transaction.commit(); // 커밋 시 UPDATE 쿼리가 실행됨
em.close();
4. 영속성 컨텍스트의 장점
- 성능 최적화
- 1차 캐시를 사용하여 데이터베이스 접근 횟수를 줄임.
- 동일한 엔티티는 동일 객체로 반환하므로 메모리 사용량이 감소.
- 변경 감지
- 데이터베이스와 객체 간의 동기화를 자동으로 처리하여 개발자가 직접 SQL을 작성할 필요를 줄임.
- 트랜잭션 단위 작업 관리
- 트랜잭션 내에서 작업을 하나의 논리적 단위로 관리.
5. 영속성 컨텍스트의 주요 메서드
메서드 | 설명 |
persist(Object entity) | 엔티티를 영속 상태로 만듦 (INSERT 쿼리 예약). |
find(Class<T> entityClass, Object primaryKey) | 1차 캐시나 데이터베이스에서 엔티티 조회. |
remove(Object entity) | 엔티티를 삭제 상태로 만듦 (DELETE 쿼리 예약). |
detach(Object entity) | 엔티티를 준영속 상태로 만듦. |
merge(Object entity) | 준영속 상태의 엔티티를 다시 영속 상태로 만듦. |
flush() | 영속성 컨텍스트 변경 내용을 데이터베이스에 반영. |
clear() | 영속성 컨텍스트를 초기화 (모든 엔티티가 준영속 상태로 변경). |
6. 영속성 컨텍스트의 주의사항
- 1차 캐시 메모리 관리
- 영속성 컨텍스트는 메모리를 사용하므로, 너무 많은 엔티티를 캐싱하면 메모리 부족 문제가 발생할 수 있습니다.
- 대량의 데이터를 처리하거나 배치 작업을 할 때, 너무 많은 엔티티가 1차 캐시에 남아 있으면 메모리 부족 문제가 발생할 수 있다.
- clear()나 detach()를 적절히 사용해 관리.
- 변경 감지 비용
- 영속성 컨텍스트는 엔티티 상태를 지속적으로 비교하므로, 대량의 엔티티를 처리할 경우 성능이 저하될 수 있습니다.
- 연관 관계 처리
- 양방향 연관 관계에서 순환 참조를 조심해야 합니다. 이를 위해 항상 연관 관계의 주인을 명확히 설정해야 합니다.
clear() 코드 예시
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
// 1. 영속 상태 엔티티 조회
User user1 = em.find(User.class, 1L); // 1차 캐시에 저장
User user2 = em.find(User.class, 2L); // 1차 캐시에 저장
System.out.println("Before clear:");
System.out.println(user1.getName()); // 1차 캐시에서 조회
System.out.println(user2.getName()); // 1차 캐시에서 조회
// 2. clear() 호출로 1차 캐시 초기화
em.clear();
System.out.println("After clear:");
// 3. 다시 같은 엔티티 조회 (1차 캐시가 초기화되었으므로 DB 조회 발생)
User user1Reloaded = em.find(User.class, 1L); // DB에서 조회
User user2Reloaded = em.find(User.class, 2L); // DB에서 조회
System.out.println(user1Reloaded.getName());
System.out.println(user2Reloaded.getName());
transaction.commit();
em.close();
배치 작업에서 clear() 활용
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
for (int i = 1; i <= 1000; i++) {
User user = new User();
user.setName("User " + i);
em.persist(user); // 영속 상태로 저장
if (i % 100 == 0) { // 100개 단위로 flush 및 clear
em.flush(); // 변경 내용을 DB에 반영
em.clear(); // 1차 캐시 초기화
}
}
transaction.commit();
em.close();
clear()의 주요 효과
- 1차 캐시 초기화
- 모든 영속 상태 엔티티가 준영속 상태로 변경됩니다.
- 이후 동일한 엔티티를 조회하면 데이터베이스에서 다시 조회됩니다.
- 변경 감지 중단
- clear() 호출 후에는 변경 감지가 중단되므로, 기존 영속 상태의 엔티티를 수정하더라도 변경 사항이 데이터베이스에 반영되지 않습니다.
- 트랜잭션 범위 내에서도 새로운 작업 가능
- 현재 작업을 종료하고 새로운 작업을 시작할 때 유용합니다.
7. 영속성 컨텍스트를 활용한 실무 전략
- 1차 캐시 활용
- 같은 트랜잭션 내에서 동일한 데이터를 반복 조회할 때 성능 최적화를 위해 적극 활용.
- 플러시 전략 설정
- FlushMode를 AUTO 또는 COMMIT으로 설정하여 불필요한 플러시를 방지.
- 배치 작업에서 메모리 관리
- 대용량 데이터를 처리할 경우, 일정 주기마다 clear()로 캐시를 비워 메모리 누수를 방지.
8. 결론
영속성 컨텍스트는 JPA의 핵심 기능으로, 객체와 데이터베이스 간의 상호작용을 효율적으로 관리하고 애플리케이션의 성능과 생산성을 높입니다. 변경 감지, 1차 캐시, 동일성 보장 등의 기능은 개발자의 작업을 단순화시키고 유지보수를 용이하게 합니다. 그러나 과도한 메모리 사용이나 잘못된 연관 관계 설정을 조심해야 합니다.