영속성 컨텍스트란?
영속성 컨텍스트는 JPA(Java Persistence API)에서 엔티티의 생명주기를 관리하고 저장소처럼 엔티티를 캐싱하는 역할을 하는 메모리 공간이다. 이 컨텍스트는 엔티티 매니저(EntityManager)에 의해 관리되고, 애플리케이션에서 데이터베이스로의 접근을 효율적으로 제어한다.
카페로 예를 들어 보겠다.
매일 같은 시간에 A는 아이스 아메리카노를 시킨다고 가정하자.
카페에 가서 A가 "아이스 아메리카노 한 잔 주세요"라고 주문한다. 이때 바리스타는 주문한 것을 기억하기 위해 메모를 하거나 POS 시스템에 입력한다.
다음 날 같은 카페에 다시 갔는데 지난번과 같은 바리스타가 일하고 있다. A가 또 "아이스 아메리카노 한 잔 주세요"라고 주문하려고 하니까, 바리스타가 "아, 지난번에도 아이스 아메리카노 주문하셨죠? 오늘도 같은 걸로 드릴까요?"라는 상황이다.
영속성 컨텍스트 이해해 보기
- 이 카페의 바리스타는 영속성 컨텍스트처럼 A가 이전에 주문한 내용을 기억하고 있다. 그래서 다시 묻지 않고 A가 이전에 주문한 것과 동일한 아이스 아메리카노를 바로 준비해 줄 수 있다.
- 1차 캐시와 비슷하게, 이 바리스타는 한 번 기억한 주문(엔티티)을 빠르게 떠올릴 수 있고, 그래서 다시 데이터베이스(메뉴판)를 확인할 필요가 없는 거다.
- 만약 A가 바리스타에게 "오늘은 라떼로 바꿀게요"라고 하면, 바리스타는 기존에 기억한 내용을 업데이트(변경 감지)하게 되고, 영속성 컨텍스트처럼 새로운 정보를 업데이트해 줄 수 있다.
이런 식으로 이해하면, 영속성 컨텍스트의 주요 기능을 쉽게 이해할 수 있을 거라고 생각한다.
주요 기능
1차 캐시 (First-Level Cache)
영속성 컨텍스트는 엔티티를 메모리 내에 저장하여, 동일한 엔티티에 대한 중복 조회를 방지한다. 예를 들어, 한 번 조회된 엔티티는 메모리에 캐싱되기 때문에, 이후 같은 엔티티를 조회할 때 데이터베이스에 재요청하지 않고도 빠르게 접근할 수 있다.
변경 감지 (Dirty Checking)
영속성 컨텍스트는 엔티티의 변경 사항을 감지하고, 트랜잭션이 커밋될 때 변경된 부분만을 데이터베이스에 반영한다. 이를 통해, 불필요한 데이터베이스 업데이트를 줄이고 데이터의 일관성을 유지할 수 있다.
동일성 보장 (Identity Assurance)
영속성 컨텍스트는 같은 엔티티를 여러 번 조회하더라도 동일한 인스턴스임을 보장한다. 즉, 두 번 조회한 엔티티가 메모리 내에서 같은 객체로 취급되기 때문에, 객체 비교 시 == 연산으로도 같은 객체임을 확인할 수 있다.
지연 쓰기 (Deferred Write):
엔티티의 생성, 수정, 삭제 등의 변경 사항은 바로 데이터베이스에 반영되지 않고, 트랜잭션이 커밋될 때까지 지연된다. 이를 쓰기 지연 전략이라고 하는데, 여러 데이터 변경 작업을 한꺼번에 처리함으로써 데이터베이스 성능을 최적화할 수 있다.
엔티티 생명주기
엔티티는 JPA에서 다음과 같은 4가지 상태로 관리된다.
비영속 (Transient)
영속성 컨텍스트에 포함되지 않은 상태로, 데이터베이스와 전혀 관련이 없는 엔티티 상태.
영속 (Persistent)
영속성 컨텍스트에 포함되어 관리되는 상태로, 데이터베이스와 동기화되어 있는 상태.
준영속 (Detached)
한때 영속 상태였지만 현재는 영속성 컨텍스트에서 분리된 상태. 즉, 데이터베이스에 연결된 상태는 아니지만, 필요시 다시 영속 상태로 전환될 수 있다.
삭제 (Removed)
영속성 컨텍스트에서 삭제된 상태로, 트랜잭션이 커밋되면 데이터베이스에서도 삭제될 예정.
플러시(Flush)란?
애플리케이션이 엔티티를 생성, 수정, 삭제하려면 트랜잭션을 먼저 시작해야 한다. 그런 다음 엔티티 매니저(EntityManager)를 통해 persist, remove 같은 메서드를 호출하여 엔티티의 상태를 변경할 수 있다.
엔티티 매니저는 이러한 변경 사항들을 바로 데이터베이스에 반영하지 않고, 먼저 영속성 컨텍스트에 임시로 저장해 둔다. 이 메모리 내 저장소를 1차 캐시라고 한다.
트랜잭션이 종료될 때 애플리케이션이 커밋(commit)을 요청하면, 영속성 컨텍스트에 저장된 모든 엔티티의 변경 사항이 한꺼번에 SQL로 변환된다. 그런 다음 이 SQL 명령들은 데이터베이스에 전송되어 커밋되고 반영된다.
플러시(Flush)는 영속성 컨텍스트에 모아두었던 엔티티 상태 변경 사항들을 데이터베이스와 동기화하는 과정이다. 이를 위에서 언급한 주요 기능 중 하나인 쓰기 지연(write-behind) 전략이라고 하는데, 트랜잭션 내에서 여러 변경 사항을 메모리에 모아두었다가 한꺼번에 반영하는 방식이다.
왜 플러시(Flush)와 쓰기 지연(Write-behind)을 사용하는가?
트랜잭션 최적화
물리적 데이터베이스 트랜잭션은 가능한 한 짧게 유지하는 것이 좋다. 트랜잭션이 길어지면 락 경합(lock contention)이 발생하여 대규모 요청 처리가 어려워질 수 있다.
성능 향상
여러 데이터 변경 작업을 한 번에 모아서 처리하면, 데이터베이스와의 상호작용 횟수를 줄일 수 있다. 이를 통해 애플리케이션 성능이 개선된다.
플러시(Flush)가 발생하는 조건
트랜잭션 커밋
트랜잭션이 커밋될 때 플러시가 발생하는 것이 일반적이다.
명시적 플러시 요청
애플리케이션 코드에서 flush() 메서드를 직접 호출할 때도 플러시가 발생한다.
쿼리 실행 전
JPQL 또는 네이티브 쿼리를 실행하기 직전에도 플러시가 자동으로 발생하며, 최신 엔티티 상태가 반영된 결과를 얻을 수 있도록 해준다.
마치며
영속성 컨텍스트는 단순히 논리적 개념이다. 트랜잭션 내에서 사용되는 엔티티를 기억하고 그 엔티티의 생명주기를 관리한다면 그건 뭐든 간에 영속성 컨텍스트라고 말할 수 있다. 그리고 엔티티 매니저는 영속성 컨텍스트를 사용할 수 있는 메서드를 제공하는 인터페이스이다.
'WEB' 카테고리의 다른 글
스프링부트의 Tomcat과 Thread Pool (0) | 2024.09.10 |
---|---|
트랜잭션이란? (0) | 2024.09.05 |
JUnit을 사용해서 Java 단위 테스트 하기 (0) | 2024.09.02 |
RDB와 NoSQL의 차이점 (3) | 2024.09.01 |
Java Thread란 무엇일까? (1) | 2024.08.30 |
SubModule이란? (0) | 2024.08.28 |
전략 패턴을 사용해 로그인 추상화 하기 (0) | 2024.08.08 |
@NoArgsConstructor 액세스 레벨을 PROTECTED로 하는 이유 (1) | 2024.06.09 |