영속성 컨텍스트(Persistence Context)란?
엔티티 객체들을 관리하고, 데이터베이스와의 상태를 동기화하는 일종의 저장소이다. 쉽게 말해, 애플리케이션이 실행되는 동안 데이터베이스에서 가져온 엔티티 객체들을 임시로 보관해 두는 메모리 공간이다. 이 공간에 있는 객체들은 "영속 상태"로 관리되며, 이 상태에 있는 동안 엔티티 객체의 변화는 자동으로 데이터베이스에 반영된다.즉, 이것이 있기 때문에 데이터베이스에서 효과적으로 데이터를 가져올 수 있고, 엔티티를 편하게 사용할 수 있다.
핵심 키워드
- 영속성 컨텍스트: 엔티티 객체를 관리하고, 데이터베이스와 동기화하는 임시 저장소.
- 영속 상태: 영속성 컨텍스트에 의해 관리되는 상태, 데이터베이스와의 동기화가 이루어짐.
추가적인 설명을 한다면 영속성 컨텍스는 엔티티 매니저가 활용, 관리하며 그 대상은 엔티티(Entity)가 된다.
엔티티 매니저와 영속성 컨텍스트의 관계를 다시한번 살펴 보자.
엔티티 매니저(Entity Manager)
- 엔티티 매니저는 JPA에서 제공하는 주요 인터페이스로, 영속성 컨텍스트를 통해 엔티티의 생명주기를 관리한다. 엔티티 매니저는 엔티티를 생성, 조회, 수정, 삭제하는 작업을 수행하며, 이 모든 작업은 영속성 컨텍스트 내에서 이루어진다.
영속성 컨텍스트(Persistence Context)
- 엔티티 매니저가 관리하는 엔티티 객체들을 저장하고 관리하는 공간이다. 이 컨텍스트는 데이터베이스와 동기화되며, 엔티티 객체의 상태를 추적한다. 예를 들어, 엔티티의 속성이 변경되면 이 변경 사항은 영속성 컨텍스트에 반영되고, 트랜잭션이 완료되면 데이터베이스에 자동으로 반영된다.
엔티티(Entity)
- 영속성 컨텍스트에 의해 관리되는 자바 객체이다. 엔티티가 영속성 컨텍스트에 포함될 때, 이를 영속 상태 라고 한다. 이 상태에서 엔티티는 데이터베이스와 연결된 상태이며, 변경사항이 자동으로 데이터베이스에 동기화된다.
영속성 컨텍스트에서 엔티티는 4가지 상태를 가질 수 있다.
비영속 상태
엔티티가 영속성 컨텍스트와 전혀 연관되지 않은 상태이다. 단순히 자바 객체로만 존재하며, 데이터베이스와 연결되지 않는다.
User user = new User("홍길동", "hong@example.com");
영속 상태 (Persistent State)
- 엔티티가 영속성 컨텍스트에 의해 관리되는 상태이다. 이 상태에서는 엔티티가 데이터베이스의 레코드와 매핑되어 있으며, 엔티티의 상태 변화가 자동으로 데이터베이스에 반영된다.
- 엔티티 매니저는 이 상태의 엔티티를 추적하고, 트랜잭션이 종료될 때 데이터베이스에 자동으로 변경 사항을 반영한다.
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
User user = new User("홍길동", "hong@example.com");
em.persist(user); // 영속성 컨텍스트에 user 객체를 저장
em.getTransaction().commit();
준영속 상태 (Detached State)
- 엔티티가 한 번 영속 상태였지만, 더 이상 영속성 컨텍스트에 의해 관리되지 않는 상태이다. 이 상태에서는 엔티티 매니저가 더 이상 해당 객체를 추적하지 않으며, 변경 사항도 데이터베이스에 반영되지 않는다.
- 이 상태로 전환되는 경우는 EntityManager.clear(), EntityManager.detach(), 또는 EntityManager.close() 등을 호출할 때이다.
User user = em.find(User.class, 1L); // 영속 상태
em.detach(user); // 준영속 상태로 전환
user.setName("김철수"); // 데이터베이스에 반영되지 않음
삭제 상태 (Removed State)
- 엔티티가 영속성 컨텍스트에 의해 관리되지만, 삭제가 예약된 상태이다. 트랜잭션이 커밋되면 해당 엔티티는 데이터베이스에서 삭제된다.
- 이 상태로 전환된 엔티티는 데이터베이스에 더 이상 존재하지 않으며, 영속성 컨텍스트에서도 제거된다.
User user = em.find(User.class, 1L); // 영속 상태
em.remove(user); // 삭제 상태로 전환
: 트랜잭션이 커밋될 때 데이터베이스에서 해당 레코드가 삭제
영속성 컨텍스트의 기본적인 특징 (반드시 숙지)
1차 캐시(First-Level Cache)
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있다. 이때 캐시의 키는 엔티티의 @Id 어노테이션이 달린 기본 키 역할을 하는 식별자이며, 값은 엔티티이다.
엔티티를 조회하면 1차 캐시에서 데이터를 조회하고 값이 있으면 반환한다. 값이 없으면 데이터베이스에서 조회해 1차 캐시에 저장한 다음 반환한다. 이를 통해 데이터를 조회할 때 데이터베이스를 거치지 않아도 되므로, 빠르게 데이터를 조회할 수 있다.
쓰기지연(Write-Delay)
쓰기 지연은 영속성 컨텍스트가 데이터베이스에 직접적으로 즉시 쿼리를 보내지 않고, 트랜잭션이 커밋될 때까지 쿼리들을 모아서 처리하는 기법이다. 이를 통해 성능을 최적화하고, 트랜잭션 내에서 데이터의 일관성을 유지하는 데 중요한 역할을 한다.
성능 최적화 SQL 쿼리를 한 번에 모아서 전송하기 때문에, 네트워크 통신 비용이 줄어들고, 데이터베이스 입출력 횟수를 줄여 성능이 향상.
일관성 유지 트랜잭션이 끝날 때까지 데이터베이스에 반영되지 않으므로, 트랜잭션 내에서 데이터 일관성을 유지할 수 있습니다.
변경 감지 (Dirty Checking)
트랜잭션을 커밋하면 1차 캐시에 저장되어 있는 엔티티의 값과 현재 엔티티의 값을 비교해서 변경된 값이 있다면 변경 사항을 감지해 변경된 값을 데이터베이스에 자동으로 반영한다. 이를 통해 쓰기 지연과 마찬가지로 적당한 묶음으로 쿼리를 요청할 수 있고, 시스템의 부담을 줄일 수 있다.
실행 흐름 요약
트랜잭션을 커밋할 때, JPA는 변경 감지를 통해 1차 캐시에 있는 엔티티와 현재 엔티티의 상태를 비교합니다. 변경 사항이 있으면, 해당 엔티티에 대해 UPDATE 쿼리를 생성하고, 이를 트랜잭션 커밋 시점에 데이터베이스에 반영합니다. 이를 통해, 쓰기 지연과 결합하여 효율적으로 쿼리를 처리하고 시스템 부담을 줄일 수 있습니다.
지연 로딩(Lazy Loading)
지연로딩은 쿼리로 요청한 데이터를 애플리케이션에서 바로 로딩하는 것이 아니라 필요할 때 쿼리를 날려 데이터를 조회하는 것을 의미한다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(fetch = FetchType.LAZY)
private List<Order> orders;
// getters and setters
}
객체 초기화 시점
- JPA에서 엔티티를 조회할 때, 연관된 다른 엔티티나 컬렉션에 대해 실제 데이터를 즉시 로드하지 않고, 해당 객체에 대한 프록시 객체(가짜 객체)를 대신 반환한다.
- 이 프록시 객체는 실제 데이터베이스 쿼리가 발생할 때까지 데이터를 불러오지 않는다.
필요할 때 데이터 조회
- 연관된 객체나 컬렉션의 데이터가 실제로 접근되는 시점(예: getter 메서드 호출 등)에 데이터베이스에서 쿼리가 실행되어 필요한 데이터를 가져온다.
성능 최적화
- 지연 로딩을 통해 초기화 시점에서 불필요한 데이터베이스 접근을 방지하고, 메모리 사용량을 줄일 수 있다. 이는 성능을 최적화하는 데 중요한 역할을 한다.
즉시 로딩(Eager Loading)
지연 로딩(Lazy Loading)의 반대 개념은 즉지 로딩이란 개념도 있다. 엔티티를 조회할 때, 연관된 모든 데이터도 함께 즉시 로드하는 방식이다. 즉, 엔티티가 처음 조회되는 시점에 그와 관련된 모든 연관 엔티티들도 한꺼번에 데이터베이스에서 가져온다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(fetch = FetchType.EAGER) // 즉시 로딩 설정
private List<Order> orders;
// getters and setters
}
초기화 시점
- 엔티티가 처음 로드될 때 연관된 모든 엔티티나 컬렉션도 함께 로드된다.
- 이로 인해 데이터베이스에 필요한 모든 쿼리가 한 번에 실행되며, 추가적인 데이터베이스 접근이 필요 없다.
성능
- 연관된 데이터가 많을 경우, 처음 로딩 시점에서 많은 데이터를 가져오게 되므로 초기 로딩 시간이 길어질 수 있다.
- 하지만 이후에는 추가적인 데이터베이스 접근이 필요 없기 때문에, 이후의 접근 속도가 빨라지는 장점이 있다.
지연 로딩과 즉시 로딩은 각기 장단점이 있으며, 상황에 따라 적절한 전략을 선택해서 사용할 수 있다.
'Spring boot > Blog 프로젝트 만들기(JPA)' 카테고리의 다른 글
PUT 방식에 이해 및 실습 (0) | 2024.09.30 |
---|---|
단방향, 양방향 매핑에 대한 이해 (0) | 2024.09.30 |
엔티티 매니저에 대해 알아보자. (2) | 2024.09.30 |
JPA 와 하이버네이트란? (1) | 2024.09.30 |
스프링 부트 DB 접근 기술 ORM (1) | 2024.09.30 |