•
설명: 영속성 컨텍스트는 EntityManager(Hibernate에서는 Session)가 관리하는 1차 캐시로, 트랜잭션 내에서 엔티티의 상태를 추적하고 데이터베이스 동기화를 지연(Write-Behind)하여 성능을 최적화한다. 이는 JPA/Hibernate의 핵심 메커니즘으로, 데이터 일관성과 성능을 동시에 보장한다.
•
역할:
◦
캐싱: 동일 엔티티를 반복 조회 시 데이터베이스 접근 없이 1차 캐시에서 반환, 애플리케이션 수준 반복 읽기(Repeatable Read) 보장. 예: 동일 트랜잭션 내에서 동일 ID로 조회 시 추가 쿼리 없이 캐시 사용.
◦
변경 감지(Dirty Checking): 엔티티의 상태 변경을 추적, 플러시 시 수정된 데이터를 SQL 쿼리(UPDATE, INSERT, DELETE)로 변환.
◦
쓰기 지연: persist, merge, remove 호출 시 즉시 SQL을 실행하지 않고, 플러시 시점에 배치 처리하여 데이터베이스 왕복을 최소화.
•
내부 구조:
◦
엔티티는 Map<EntityUniqueKey, Object>에 저장되며, 각 엔티티는 로드된 상태(Loaded State, 스냅샷)와 함께 관리된다.
◦
로드된 상태는 더티 체킹 시 비교 기준으로 사용되어 변경된 속성을 감지.
◦
스냅샷 예시: 엔티티 로드 시 초기 상태를 저장, 플러시 시 현재 상태와 비교하여 변경된 속성만 SQL로 변환.
•
실무 예시: 동일 트랜잭션 내에서 동일 엔티티 조회 시 캐시 활용으로 쿼리 수 감소.
Post post = entityManager.find(Post.class, 1L); // 데이터베이스 조회
Post samePost = entityManager.find(Post.class, 1L); // 1차 캐시에서 반환, 추가 쿼리 없음
Java
복사
7.2 플러시 (Flushing)
•
설명: 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 과정이다. 더티 체킹을 통해 변경된 엔티티를 감지하고, 쓰기 지연 SQL 저장소에 INSERT, UPDATE, DELETE 쿼리를 등록하여 실행 준비한다. 실제 데이터베이스 반영은 트랜잭션 커밋 시 완료된다.
•
플러시 발생 시점:
◦
명시적 플러시: entityManager.flush()를 직접 호출하여 즉시 동기화.
◦
트랜잭션 커밋: 트랜잭션 커밋 직전에 자동 플러시 발생, 모든 변경 사항 반영.
◦
JPQL/Criteria 쿼리: 쿼리 실행 전, 관련 테이블(쿼리 공간, Query Space)에 변경 사항이 있으면 자동 플러시. 이는 데이터 일관성을 보장하기 위함.
◦
네이티브 SQL 쿼리: 쿼리 공간 미등록 시 항상 플러시, 등록 시 관련 테이블만 플러시(예: addSynchronizedEntityClass 사용).
•
플러시 모드:
◦
AUTO (기본값): 트랜잭션 커밋 또는 JPQL 쿼리 실행 전 플러시. Hibernate 5.2 이후 JPA 표준과 동기화되어 쿼리 관련 테이블만 플러시.
◦
COMMIT: 트랜잭션 커밋 시에만 플러시, 쿼리 전 플러시 생략으로 성능 향상.
◦
ALWAYS (Hibernate 전용): 모든 쿼리 실행 전 플러시, 불필요한 플러시로 성능 저하 위험.
◦
MANUAL (Hibernate 전용): 명시적 플러시만 수행, 고급 최적화 시 사용.
◦
설정 예시:
entityManager.setFlushMode(FlushModeType.COMMIT); // JPA
session.setHibernateFlushMode(FlushMode.MANUAL); // Hibernate
Java
복사
•
실무 예시: JPQL 실행 전 자동 플러시로 데이터 일관성 보장.
Post post = new Post("Hibernate");
post.setId(1L);
entityManager.persist(post);
List<Post> posts = entityManager.createQuery("SELECT p FROM Post p", Post.class).getResultList();
// JPQL 실행 전 자동 플러시, INSERT 쿼리 실행
Java
복사
7.3 성능 최적화
•
쓰기 지연과 배치 처리:
◦
persist, merge, remove 호출 시 SQL을 즉시 실행하지 않고, 플러시 시점에 배치 처리로 데이터베이스 왕복을 최소화.
◦
배치 처리 설정: hibernate.jdbc.batch_size=50, hibernate.order_inserts=true, hibernate.order_updates=true로 동일 테이블의 SQL을 그룹화하여 성능 향상.
◦
ID 생성 연계: 대규모 배치 처리 시 GenerationType.SEQUENCE(예: allocationSize=50)로 ID를 사전 할당, 플러시와 결합하여 배치 삽입 최적화. 예: 100만 건 삽입 시 hibernate.jdbc.batch_size=100과 주기적 flush로 처리 속도 10배 향상.
@Entity
public class Post {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "post_seq")
@SequenceGenerator(name = "post_seq", sequenceName = "post_sequence", allocationSize = 50)
private Long id;
private String title;
}
Java
복사
•
더티 체킹 최적화:
◦
기본 더티 체킹은 Java 리플렉션으로 모든 속성을 비교, 엔티티 또는 속성 수가 많을 경우 성능 저하(예: 1000개 엔티티 처리 시 수백 ms 소요).
◦
바이트코드 향상(@EnableDirtyTracking)으로 변경된 속성만 추적, 플러시 시간 단축.
줄 바꿈 활성화텍스트로 복사
@Entity
@EnableDirtyTracking
public class Post {
@Id @GeneratedValue
private Long id;
private String title;
}
Java
복사
•
영속성 컨텍스트 관리:
◦
대량 데이터 처리(예: 100만 건) 시 영속성 컨텍스트 크기 증가로 메모리 문제(OutOfMemoryError) 발생 가능.
◦
주기적 flush와 clear 호출로 관리, 메모리 사용량 감소 및 성능 유지.
for (int i = 0; i < 10000; i++) {
Post post = new Post("Post " + i);
entityManager.persist(post);
if (i % 1000 == 0) {
entityManager.flush(); // 변경 사항 동기화
entityManager.clear(); // 영속성 컨텍스트 초기화
}
}
Java
복사
•
플러시 모드 최적화:
◦
FlushMode.COMMIT으로 쿼리 실행 전 불필요한 플러시 방지, 트랜잭션 처리 속도 향상.
◦
네이티브 SQL 쿼리에서 쿼리 공간 등록으로 플러시 최소화, 성능 최적화.
session.createSQLQuery("SELECT * FROM post")
.addSynchronizedEntityClass(Post.class);
Java
복사
•
실무 팁:
◦
대량 배치 처리 시 MANUAL 모드와 네이티브 쿼리 조합으로 플러시 제어, 메모리 사용량 감소.
◦
Hypersistence Optimizer로 불필요한 플러시(예: RedundantSessionFlushEvent) 진단.
◦
예: 100만 건 데이터 마이그레이션 시, hibernate.jdbc.batch_size=100, 주기적 flush/clear, GenerationType.SEQUENCE 조합으로 처리 시간 10배 단축.
•
모니터링: Hibernate 통계(hibernate.generate_statistics=true)로 플러시 시간, 쿼리 실행 횟수, 더티 체킹 비용 분석.
7.4 권장사항
•
영속성 컨텍스트 크기를 최소화, 주기적 flush와 clear로 메모리 관리.
•
FlushMode.COMMIT 또는 MANUAL로 불필요한 플러시 방지, 쿼리 성능 최적화.
•
바이트코드 향상(@EnableDirtyTracking)으로 더티 체킹 성능 개선.
•
네이티브 쿼리 사용 시 쿼리 공간 명시(addSynchronizedEntityClass)로 플러시 최소화.
•
Hibernate 통계(hibernate.generate_statistics=true)로 플러시 시간 및 배치 처리 성능 분석, 병목 지점 식별.
•
대량 데이터 처리 시 hibernate.jdbc.batch_size(예: 50~100)와 GenerationType.SEQUENCE(예: allocationSize=50)로 ID 생성 및 삽입 최적화.
•
Hypersistence Optimizer로 플러시 및 배치 처리 비효율성(예: 불필요한 플러시, 더티 체킹 오버헤드) 진단.
•
테스트 환경에서 Testcontainers를 사용해 대량 데이터 처리 시나리오(예: 10만 건 삽입)로 성능 검증.