영속성 컨텍스트
영속성 컨텍스트란 jpa에서 엔티티를 db에 반영하기 전에 영속화하는 공간이다.
대체적으로 영속성 컨텍스트를 엔티티를 영구 저장하는 공간이라고 하는데 이는 물리적인 개념이 아니라
논리적인 개념으로 실제 영속성 컨텍스트에서 db에 존재하는 모든 엔티티를 저장하는게 아닌 필요할 때마다
가져와서 영속성 컨텍스트에서 사용하므로 논리적으로는 영구 저장되는 공간이라고 하는 것 같다.
(실제 물리적으로는 트랜잭션 단위의 휘발성 메모리공간)
서버단에 존재하는 가상의 DB역할을 하는 일종의 메모리 공간이며 엔티티의 변화를 감지하여
DB에 반영전 객체로 우선적으로 작업하는 공간이라고 생각하면 될 것 같습니다.
영속성 컨텍스트의 구조를 보면 위와 같이 영속성 컨텍스트는 EntityManger 안에 존재하며
"1차 캐시 공간"과 "SQL 저장소"로 이루어져 있다.
1차 캐시 안에서 사용자가 조회 및 수정한 엔티티가 관리되고 SQL저장소는 사용자의 명령한 SQL저장소에
저장되어 트랜잭션을 커밋하는 시점에 한번에 DB에 반영된다.(옵션에 따라 다름)
1차 캐시 공간에서 엔티티를 관리하니 내가 사용하려는 엔티티와 동일한 엔티티가 캐시공간에 있다면
DB에 접근하지 않고 영속성 컨텍스트에 있는 엔티티를 사용할 수 있는것이다.
여기에서 의문이 들 수 있을만한 내용이 엔티티의 동일 여부를 어떻게 판단하는지,
캐시공간이 전역적으로 사용되는 것인지, 캐시의 지속기간이 어느정도인지 궁금할 수 있을 것이다.
정답부터 말을 하면 엔티티의 동일여부는 오직 식별자(객체의 id, 테이블에서 pk)로 판단하고,
캐시공간은 전역적이지않고 트랜잭션 단위이며 마찬가지로 캐시의 지속기간도 트랜잭션 단위이다.
따라서 영속성 컨텍스트는 논리적으로는 영구적이지만 실제로는 트랜잭션 단위의 휘발성 메모리 공간으로
생각하면 될 것이다.
클라이언트의 요청부터 DB까지 반영되는 과정을 설명해보겠다.
위의 도식화해본 그래프를 참조해보면 클라이언트의 요청에 서버에 들어오게 되면
클라이언트의 요청 작업을 쓰레드에게 EntityFactory가 EntityManager를 트랜잭션이 시작 될 때 할당한다.
만약 사용자가 DB에 엔티티 쓰기 작업을 하려 한다면
내부적으로 해당 엔티티의 식별자값을 가져와 select를 먼저 수행후 select를 통해 얻은 엔티티와
사용자가 가져온 엔티티를 비교하여 변화된 필드를 감지한다.
엔티티의 변화를 감지하는 과정을 "더티 체킹"이라고 한다.
쓰기 작업 전 select를 통해 얻은 엔티티에 대한 스냅샷을 생성하여 사용자가 가져온 엔티티와
더티체킹하여 변화가 일어났다면 update 쿼리를 만들어 바로 DB로 접근하지 않고 SQL 저장소에 쿼리를 저장한다.
이렇게 쿼리가 바로 실행되지 않고 SQL 저장소에 저장해두고
트랜잭션이 끝나면 한번에 실행되는 과정을 "쓰기 지연"이라고 한다.
쓰기 지연을 사용하게 되면 좋은점이 서버와 DB가 쿼리를 실행할 때 마다 접근하지 않고
쓰기 작업(inser, update, delete) 시에만 접근하여 한번에 처리하기 때문에 성능과 속도면에서 큰 효율을 얻을 수 있다.
다만 상황에 따라 쓰기 지연이 발생할 수 도 있고 발생하지 않을 수도 있다.
이 부분은 굉장히 많은 상황에서 다르게 작동하여 다른 포스팅에서 테스트 결과와 함께 통해 공유 하겠습니다.
이렇게 해서 모든 작업이 끝나고 트랜잭션이 커밋되면 최종적으로 영속성 컨텍스트의 SQL 저장소에 있는 쿼리가
DB에 반영이 되고 영속성 컨텍스트는 트랜잭션 종료와 함께 파기 됩니다.
영속성 컨텍스트의 생명주기와 관련하여 좀 더 알아보자면
영속성 컨텍스트는 트랜잭션 시작시점에서 부터 종료시점까지 유효하므로
생명주기를 하나의 트랜잭션 단위라고 할 수 있고 그에 따라
하나의 트랜잭션 범위에 여러 EntityManager가 묶이면 같은 트랜잭션 범위 안에 있는 EntityManager는
하나의 영속성 컨텍스트를 공유하고 (예시.세 엔티티매니저가 모두 하나의 영속성 컨텍스트 공유)
@Transactional
public test(){
MemberRepository.save(member);
TeamRepository.save(team);
CompanyRepository.save(company);
}
같은 EntityManager를 사용하더라도 트랙잭션 단위가 다르면 당연히 영속성 컨텍스트가 다르다.
예를 들어 위의 test() 메서드를 두 사용자가 동시에 실행하더라도 서로 사용하는 영속성 컨텍스트는 다르다.
위에서 설명한 부분 중 1차캐시 공간에서 엔티티는 오직 식별자로 구분된다는 내용과 쓰기 지연 현상에 대한
설명이 부족한것 같아 별도로 포스팅을 하겠습니다.
저 또한 더 많은 테스트를 통해 알아봐야할 부분인 것 같으니 테스트결과와 함께 공유하도록 하겠습니다.
추가적으로 EAGER와 LAZY의 차이, N+1 문제에 대해서도 공유할 예정입니다.
(실제 프로젝트 하며 겪은 문제)
++++++++++++ 참고사항 ++++++++++++
"JPA에 right outer join은 존재하지 않는다."
jpa는 mybatis와 다르게 데이터베이스 중심이 아닌 객체중심이기 때문에 right outer join의 개념은 없습니다.
어찌보면 당연한 말이지만 mybatis를 사용하는 저에게는 의구심이 드는 부분이었는데
엔티티는 오직 식별자로 구분하기 때문에 내가 가져오려는 엔티티의 식별자가 없는데 가져온다는 것은
객체중심 프로그래밍에서 존재하지 않는 것 같습니다.
데이터베이스 중심에서는 null이어도 다른 칼럼이 조회조건이 될 수 있어 가져올 수 있는데
엔티티의 경우에는 오직 식별자로 엔티티를 구분하니 조회하는 객체의 식별자가 없으면
조회불가, 즉 null이 허용되지 않습니다.
'Framework & Lib & API > JPA' 카테고리의 다른 글
코틀린 JPA Entity 작성법 (0) | 2024.03.13 |
---|---|
쿼리dsl, 코틀린 case when sum 구문에서 사용하기 (1) | 2024.03.06 |
jpa 연관관계 EAGER와 LAZY(etc, 실제 겪은 문제들) (0) | 2022.06.26 |