Backend/🍃Spring
[Spring] 영속성 컨텍스트 (Persistence Context)
seungwookim
2021. 11. 30. 14:17
인프런 김영한님의 JPA 강의와 티스토리 글을 읽고 정리하였습니다.
엔티티 (매니저) 팩토리와 엔티티 매니저
- 엔티티 매니저 팩토리
- 엔티티 매니저를 만드는 공장
- 엔티티 매니저 팩토리는 생성하는 비용이 크기 때문에 한 개만 만들어 전체 어플리케이션에서 공유한다.
- 여러 스레드가 동시에 접근해도 안전하다.
- 엔티티 매니저
- 엔티티의 CRUD 등 엔티티와 관련된 모든 일 처리
- 엔티티 매니저는 동시에 접근하면 안 되므로(동시성 문제) 스레드 간에 공유하지 않는다.
- 동시성 문제: 두 개 이상의 작업이 공통된 자원에 접근하고자 할 때 발생하는 문제
- "일관성 없는 읽기"와 "손실되는 업데이트" 문제가 있음.
- 동시성 문제: 두 개 이상의 작업이 공통된 자원에 접근하고자 할 때 발생하는 문제
영속성 컨텍스트란?
- 엔티티를 영구 저장하는 환경
엔티티의 상태(생명 주기)
(사진 출처: 티스토리)
- 비영속
- 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속
- 영속성 컨텍스트에서 관리되는 상태
- 엔티티 매니저가 엔티티를 영속성 컨텍스트에 저장하는 순간부터 영속 상태가 되어 영속성 컨텍스트에 의해 관리받는다.
- 트랜잭션이 커밋되는 순간 영속성 컨텍스트의 엔티티들이 DB에 반영된다.
- 준영속
- 영속성 컨텍스트에 저장되었다가 분리되어 더이상 영속성 컨텍스트에 의해 관리되지 않는 상태
- 삭제
- 삭제된 상태
영속성 컨텍스트의 특징
- 식별자(Identifier)
- 영속성 컨텍스트는 엔티티를 식별자 값(@Id)으로 구분한다. 따라서 영속 상태의 엔티티는 반드시 식별자 값이 존재해야 한다.
- Flush
- JPA는 트랜잭션이 커밋되는 순간 영속성 컨텍스트에 저장된 엔티티를 DB에 반영하는 Flush(플러시)를 수행한다.
영속성 컨텍스트의 장점
- 1차 캐시
- 영속성 컨텍스트 내부에 캐시가 존재
- 영속 상태의 엔티티들은 모두 이곳에 Map(key: @Id, value: 엔티티 인스턴스)으로 저장
- 엔티티를 조회할 때 먼저 1차 캐시(메모리 상주)에서 찾고 캐시에 존재하지 않으면 DB를 조회한다.
- 데이터베이스에 접근하지 않고 1차 캐시에 있는 엔티티를 반환하므로 데이터베이스에 접근하는 빈도를 줄일 수 있다.
- 단, 하나의 트랜잭션 안에서만 효과가 있으므로 성능상 이점이 크지는 않다.
- 동일성(Identity 보장)
- 1차 캐시로 인해 조회 시 같은 엔티티 인스턴스를 반환하기 때문에 여러 번 조회하더라도 엔티티 인스턴스의 동일성을 보장한다.
- 트랜잭션을 지원하는 쓰기 지연(Transactional write-behind)
- 엔티티 매니저는 트랜잭션을 커밋하기 전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 모아둔다.
- 트랜잭션이 커밋될 때 모아둔 쿼리를 데이터베이스에 요청하여 반영한다.
- transaction commit -> flush : 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업.
- 변경 감지(Dirty Checking)
- 엔티티의 변경 사항을 자동으로 감지하여 데이터베이스에 반영하는 기능이다.
- 영속성 컨텍스트가 관리하는 영속 상태의 엔티티만 적용된다.
- JPA는 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 스냅샷으로 저장해둔다.
- 트랜잭션 커밋하여 flush가 동작할 때 엔티티와 스냅샷을 비교하여 변경사항을 체크하고 수정 쿼리를 생성한다.
- 수정 쿼리는 쓰기 지연 SQL 저장소로 보내지며 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 반영한다.
- 지연 로딩(Lazy Loading)
- 엔티티간 연관 관계가 맺어져 있는 엔티티를 검색할 때 연관 관계가 있는 엔티티는 프록시 데이터로 채우고 실제로 사용할 때 검색하게 하는 지연 로딩을 통해 불필요한 쿼리를 실행하지 않게 도와준다.
- 지연 로딩을 사용하는 것이 어플리케이션 성능 향상에 도움을 준다.
- 필요에 따라서는 연관 관계가 있는 엔티티와 join할 수도 있다.(ex. N+1 문제)
Flush 과정
- 변경 감지(Dirty Checking)가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 최초 스냅샷과 비교한다.
- 비교 후 수정된 엔티티에 대해 수정 쿼리를 생성하고 이는 쓰기 지연 SQL 저장소에 등록된다.
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 반영한다.
Flush 방법
- 직접 호출: em.flush()
- 자동 호출
- 트랜잭션 커밋 시점
- JPQL 쿼리 실행
- JPQL(Java Persistence Query Language): Java Persistence에서 지원하는 객체지향 쿼리