[JPA] Entity는 영속성 컨텍스트에 저장/조회한다!
🪄 Intro
이 포스팅은 단일 트랜잭션 실습 중 생긴 의문점을 해결한다.
영속성 컨텍스트의 '1차캐시' 개념을 알게 되었다.
🚨 상황
- Spring JPA를 통한 @Transactional 테스트코드 실습중!
↓ 내 테스트코드
Entity의 속성값 "YEEUN KIM"을 "SIKYONG SUNG"으로 변경하는 코드
@Test
@Transactional // 영속성 컨텍스트 안에서 관리한다.
void UPDATE_TEST() {
// Given : 엔티티를 영속성 컨텍스트에 저장
CustomerEntity customer = new CustomerEntity();
customer.setId(1L);
customer.setFirstName("YEEUN");
customer.setLastName("KIM");
repository.save(customer);
// When : 엔티티 값 변경
CustomerEntity entity = repository.findById(1L).get();
log.info("{} {}", entity.getFirstName(), entity.getLastName()); // 변경 전 값이 찍힌다
entity.setFirstName("SIKYONG");
entity.setLastName("SUNG");
CustomerEntity updated = repository.findById(1L).get();
log.info("{} {}", updated.getFirstName(), updated.getLastName()); // 변경 후 값이 찍힌다
}
🚨의문 & 상황검토
아래 로그를 보고 의문에 휩싸였다.
① commit 안한 값을 읽었어??
- 엔티티 값을 "SIKYONG SUNG"으로 업데이트했다.
- 그러나 @Transaction 메서드가 아직 완료되지 않았으므로 "SIKYONG SUNG"은 commit되지 않은 값이 아닌가? 그런데 어째서 이게 read되는 걸까?
② 현재 격리수준은??
내 상황을 검토해보았다.
- 나는 격리수준이 read commited인 H2 DB를 사용하고 있다.
- @Transactional을 통해 영속성 컨텍스트에서 관리되고 있다.
- @Transactional의 격리 수준은 default이다. 즉 사용하는 DB의 격리수준을 따른다.
- 따라서 현재 격리수준은 read commited 이다.
위 의문은 "영속성 컨텍스트"를 이해하면 해결된다!
🍒 해답
일단 의문에 코멘트를 달자면..
① commit 안한 값을 읽었어??
=> 맞다.
=> 근데 애초에 select할 때 DB에서 값을 읽는 게 아니다.
=> "영속성 컨텍스트의 1차캐시"에서 읽어옴을 알아야 한다.
② 현재 격리수준은??
=> (언급했듯) read commited
=> 단일 트랜잭션 실습일뿐만 아니라, 어차피 트랜잭션 내에서 Entity 생성하고 업뎃하고 다 하고 있다. 그래서 이 문제에서 격리수준은 딱히 신경 쓸 필요 없어보인다.
그래서 해답은 "영속성 컨텍스트의 1차 캐시"를 이해하는 것에 있다.
∨ 조회할 때는 DB가 아닌 1차캐시에서 먼저 조회한다.
- 조회시 1차캐시에 객체가 있는지 확인(PK로 확인)하고, 있다면 가져오기
- 1차캐시에 없다면 2차캐시에서 조회한다.
- 2차캐시에도 없다면 DB에서 조회하여 객체의 스냅샷를 가져온다. 그 스냅샷을 1차2차캐시에 저장한다.
∨ 저장하거나 수정할 때도 1차캐시에 변경사항을 저장한다.
∨ 참고) 위와 같은 쿼리들은 '내부의 쿼리 저장소'에 일단 저장되고, 최종적으로 트랜잭션 커밋시 한번에 수행되고 나서 DB에 반영된다. 참고로 이는 영속성 컨텍스트의 '쓰기 지연' 특징이다. 여기서는 다루지 않겠음.
🍒 코드 한줄한줄을 영속성 컨텍스트 기반으로 설명해보자.
참고) 아래에서 '영속성 컨텍스트에서 찾는다'라는 표현은,
영속성 컨텍스트의 1차캐시 or 2차 캐시에서 찾는다는 말이다.
▼ Entity를 만든다. 아직 비영속 상태의 Entity다.
CustomerEntity customer = new CustomerEntity();
customer.setId(1L);
customer.setFirstName("YEEUN");
customer.setLastName("KIM");
▼ 영속성 컨텍스트에 Entity를 save한다. 영속 상태의 Entity다.
repository.save(customer);
Entity를 save할 때 영속성 컨텍스트에서 관리된다.
참고로 save(Entity)할 때 Entity의 PK가 있으면 save, 없으면 update 쿼리가 나간다
▼ findById(PK)를 통하여 영속성 컨텍스트에서 해당 PK를 가진 객체를 찾아 가져온다.
CustomerEntity entity = repository.findById(1L).get();
▼ Entity를 update하게 되면, 변경 사항이 영속성 컨텍스트에 반영된다.
entity.setFirstName("SIKYONG");
entity.setLastName("SUNG");
▼ findById(PK)를 통하여 영속성 컨텍스트에서 해당 PK를 가진 객체를 가져온다.
CustomerEntity updated = repository.findById(1L).get();
아핫 이제 이해가 된다.
영속성 컨텍스트에 저장한다는 그 개념!
🍒 이로써 의문 해결!
처음에는 "왜 commit되지 않은 값을 읽은거야! read commited잖아!!" 라고 생각하면서 아래 로그를 의아해했지만 이제는 이해할 수 있다. commit과 상관 없다.
Entity는 영속 상태로서, 영속성 컨텍스트의 1차캐시에 반영되고, 1차캐시에서 읽어오기 때문이다!
공부에 도움이 된 글: