JPA는 DB 데이터와 Java Entity를 맵핑해주는 역할을 한다. 이 때, 영속성 컨텍스트(Persistence Context)를 중심으로 엔티티를 맵핑하고 관리한다. 따라서 영속성 컨텍스트라는 개념 없이는 JPA를 이해하기 어렵다.
영속성 컨텍스트(Persistence Context)
영속성(Persistence)의 사전적 의미는 ‘영원히 계속되는 성질이나 능력’이다. 데이터의 상태를 영구히 지속되게 하는 방법은 DB에 저장하는 방식을 떠올리기 쉽다. 하지만, 이 영속성 컨텍스트가 데이터를 관리한다는 말이 실제로 데이터를 DB에 저장하는 것은 아니다.
영속성 컨텍스트는 일종의 논리적인 영역이다. 한번 DB에서 조회해온 데이터는 영속성 컨텍스트에서 관리하며 이를 ‘영속 상태’에 있다고 한다. JPA를 사용할 때 데이터를 조회하게 되면 제일 먼저 영속성 컨텍스트에서 찾고, 여기에서 찾을 수 없으면 DB에 쿼리를 날리게 된다. 즉, 1차 캐시인 셈이다.
이러한 영속성 컨텍스트에서 객체를 관리하고 필요에 따라서는 DB에 쿼리를 날리게 되는데, 이러한 역할을 수행하는 객체가 엔티티 매니저(EntityManager)다.
private final EntityManager entityManager;
그리고, 엔티티 매니저를 만드는 역할을 하는 것이 엔티티 매니저 팩토리(EntityManagerFactory)이다. 엔티티 매니저 팩토리는 트랜잭션이 발생할 때마다 엔티티 매니저를 생성한다. 그러면 엔티티 매니저는 이미 존재하는 영속성 컨텍스트를 바라보게 된다.
영속성 컨텍스트의 구조
영속성 컨텍스트는 크게 1차 캐시 저장소와 SQL 저장소가 있다. 1차 캐시 저장소에는 영속성 컨텍스트가 관리하는 엔티티 정보. 즉, 영속 상태에 있는 엔티티 정보를 보관한다.
SQL 저장소는 DB에 접근하는 횟수를 최소화하기 위해 필요한 쿼리문을 보관해둔다. 그러다 엔티티 매니저의 flush()
를 통해 저장해둔 쿼리문들이 DB에 접근하게끔 할 수 있다.
엔티티의 생명주기
영속성 컨텍스트에서 관리되던 엔티티는 늘 영속성 상태를 유지하는 것이 아니다. 이를 엔티티의 생명주기로 설명할 수 있다. 엔티티의 생명주기는 총 4가지 단계로 나눌 수 있다.
- 비영속(new, transient) 상태: 엔티티가 영속성 컨텍스트와 관련이 없는 상태
- 영속(managed) 상태: 엔티티가 영속성 컨텍스트에 의해 관리되는 상태. 엔티티 매니저의
persist()
를 통해 영속 상태로 만들 수 있다.- 하나의 엔티티 인스턴스를
persist()
함으로써 영속 상태가 되게 하면, 해당 트랜잭션이 끝날 때(엔티티 매니저가flush()
될 때) INSERT문이 실행된다.- 이 때,
flush()
는 쉽게 말해 DB와 영속성 컨텍스트의 해당 데이터를 동기화(synchronize)한다고 생각하면 된다.
- 이 때,
- 이후에 엔티티 인스턴스를 찾아오려면 엔티티매니저의
find()
메서드를 이용할 수 있다. 이 때 SQL 저장소에는 SELECT문이 저장되게 된다. - 한 번 조회한 엔티티를 반복적으로 조회하면 이 때는 DB에 쿼리를 날리지 않고, 영속성 컨텍스트에 있는 데이터를 전달해준다. 이를 통해 조회에 대해서 큰 성능상의 이점을 취할 수 있다. 이 때 반환해주는 엔티티 인스턴스는 동일한 인스턴스이다. (주소 해시값이 동일한 완전히 같은 객체를 반환해준다.)
- 하나의 엔티티 인스턴스를
- 준영속(detached) 상태: 영속성 컨텍스트에서 관리되다가 관리되지 않는 상태가 된 것을 보고 준영속 상태라고 한다. 준영속 상태에서 다시 영속 상태로 만들기 위해서는
merge()
를 통해 가능하다.- 준영속 상태가 되는데에는 크게 3가지 경우가 있다.
- 엔티티 매니저의
detached(해당 엔티티 인스턴스)
를 통해 특정 엔티티만 준영속 상태로 만드는 경우 - 엔티티 매니저의
clear()
를 통해 영속성 컨텍스트 전체를 초기화하면, 관리되던 엔티티 객체가 모두 준영속 상태가 된다. - 영속성 컨텍스트를 닫아버리는
close()
를 사용하면 영속성 컨텍스트에서 관리되던 엔티티 객체들이 모두 준영속 상태가 된다.
- 엔티티 매니저의
- 준영속 상태가 되는데에는 크게 3가지 경우가 있다.
- 삭제(removed) 상태: 엔티티를 영속성 컨텍스트에서 즉시 삭제되고, DB에서 DELETE하는 쿼리문을 SQL 저장소에 저장하게 한다. 이 DELETE문은
flush()
될 때 DB에 호출되게 된다.
변경 감지(Dirty Checking)
영속성 컨텍스트는 관리하고 있는 엔티티 객체의 참조 주소와 함께 이전에 영속 상태로 만들었을 때의 스냅샷이 존재한다. 그래서 영속 상태에 있는 엔티티는 엔티티 매니저의 flush()
가 호출되고 실행되기 직전에 이전 상태와 비교하여 변경 사항이 존재하면 데이터가 변경되어야 한다고 판단해 DB에 UPDATE 쿼리를 날리게 된다. 따라서 데이터를 업데이트 할 때에는 별도의 UPDATE 쿼리를 실행시킬 필요가 없다.
객체가 변경 사항이 생기면 객체의 모든 데이터를 업데이트하는데(변경된 필드 값만 하지 않는다.) 변경된 필드만 업데이트 하려면 해당 엔티티에 @DynamicUpdate
를 설정하면 된다. 대략 컬럼 수가 30개 이상이면 다이나믹 업데이트가 더 빠르다고 하는데, 사실상 한 테이블에 컬럼이 30개 이상인 것은 컬럼이 잘 분배가 된 것인지(한 테이블에 모든 컬럼을 때려박은 것은 아닌지) 고민해봐야 한다.
'Backend > JPA' 카테고리의 다른 글
JPA) Example, ExampleMatcher 사용해 조건에 맞는 값 조회하기 (0) | 2024.07.05 |
---|---|
JPA) 지연 로딩 성능 최적화(N + 1 문제 해결, fetch 조인) (0) | 2024.05.25 |
Springboot + JPA) 페이지네이션(Pagenation)하기 - Pageable, PageRequest, Page (0) | 2024.04.17 |
JPA(Java Persistence API) - 도커에 올린 DB(H2)와 연동하기 (0) | 2023.11.17 |