들어가기에 앞서 JPA의 영속성 컨텍스트에 대해 공부한 내용을 정리해보고자 한다.
JPA는 사용하기 쉬운 만큼 알아야 할 내용도 무궁무진하다.
동작 원리를 정확하게는 아니지만 대강적으로 이해하고 있지 않는 이상 분명히 운영 상황에서 대처 불가능한 에러를 맞이할 것이라고 생각한다.
아래는 유명한 JPA 영속성 컨텍스트 이미지이다.
JPA는 DB를 직접 조회하는 것이 아니라 1차 캐시, 즉 영속성 컨텍스트를 우선적으로 조회한다.
따라서 DB의 I/O 부담이 적게 발생하여 성능의 이점이 있다는 것이 JPA의 장점이라 할 수 있다.
기본적으로 JPA는 Entity의 PK를 이용하여 식별을 하게 되며, 1차 캐시에는 고유 식별자 값으로 엔티티를 저장하고 있다.
JPA의 findById() 메소드를 호출하게 되면
우선, 1차 캐시에서 식별자 값으로 저장된 Entity를 찾고 1차 캐시에 존재하면 해당 객체를 리턴, 존재하지 않는다면 DB에 직접 조회해 1차 캐시에 적재한 후 해당 객체를 리턴한다.
JPA는 1차 캐시를 사용함으로서 1차 캐시에 있는 엔티티와 DB의 데이터 동일성을 보장하기 위해 많은 기능들을 제공하고 있다. JPA에서 제공하고 있는 findById() or entityManager의 find() 함수를 실행하게 되면 먼저 1차 캐시를 조회한다.
그렇다면 JPQL은 어떻게 동작할까?
먼저 JPQL은 JPQL(Java Persistence Query Language)로 SQL과 비슷한 문법을 가진 객체 지향 쿼리이다.
@Query (value = "SELECT * FROM MEMEBER WHERE NAME = :name"
, nativeQuery = true)
public Member getMemberbyName(String name);
1) JPQL을 호출하면 1차 캐시를 조회하는 것이 아니라 데이터베이스에 우선 조회한다.
2) 데이터베이스에서 조회한 값을 영속성 컨텍스트에 저장한다.
3) 저장할 때 고유 식별자 값으로 이미 영속성 컨텍스트에 존재할 경우 데이터베이스에서 조회한 신규 데이터는 버리고 영속성 컨텍스트의 데이터를 사용한다.
여기서 데이터베이스에서 조회한 신규 데이터를 버리는 이유는 동일성 때문이다.
만약, 데이터베이스에서 조회한 신규 데이터로 영속성 컨텍스트에 저장하게 된다면 이미 해당 식별자 값의 엔티티 객체를 사용하는 곳과 동일성이 깨지게 된다.
그러면 여기서 드는 생각이 내가 식별하고자 하는 엔티티 값이 이전이 이미 수정되었고 JPQL로 조회하려고 한다면 어떻게 되는것일까?
코드부터 확인하자.
Member memeber1 = memberRepository.findById("TEST1")
.orElseThrow(() -> new RuntimeException("해당 값이 Null 입니다"));
memeber1.setName("변경"); // memeber1의 name : "기존" -> "변경"
memberRepository.save(member); --- (1)
Member member2 = memberRepository.getMemberbyName("변경"); --- (2)
먼저 결과는 member2는 정상적으로 객체를 가져온다.
왜 가져올 수 있을까?
(1) 시점에서는 Transaction이 종료되지않아 flush가 실행되지 않았으므로 update 문이 1차 캐시에 쌓여있는 상태이며 DB에 반영되지 않았다.
그러나 (2) 시점에서 변경된 name 값으로 JPQL 문으로 조회했을때 결과는 정상적으로 나올것이다.
JPQL은 1차 캐시를 조회하지 않고 DB를 먼저 조회하기 때문에 변경되지 않은 결과값이 나와야 할것이다.
하지만 JPA는 동일성을 보장하기 위해 JPQL을 실행하기 전 1차 캐시에 쌓여있던 쿼리들을 flush 한 후 JPQL을 실행하도록 되어있다.
트랜잭션을 지원하는 쓰기 지연
변경 감지(Dirty Checking)
JPA는 Entity의 Update를 더티 체킹 방식으로 제공하고 있다.
더티 체킹이란 Entity의 변경 감지를 말한다. JPA가 트랜잭션이 종료되는 시점에 Entity의 변경된 값을 확인하고 update를 문을 DB에 자동으로 반영한다.
따라서 개발자가 명시적으로 Entity를 update를 하는 것이 아니라 JPA Engine에게 일임하는 것이다.
여기서 JPA는 1차 캐시에서 스냅샷(Snapshot)을 사용한다.
JPA는 Entity를 조회하여 1차 캐시에 스냅샷을 생성한다. 이후 트랜잭션이 끝나는 시점에 조회한 엔티티 객체와 스냅샷을 비교하여 변경된 값만 Update를 반영한다.
이때 스냅샷으로 관리되려면 엔티티의 상태는 영속 상태여야 한다. 또한 Trasaction이 readOnly 모드일 경우 스냅샷은 생성되지 않으므로 변경 감지는 동작하지 않는다.
엔티티는 4가지의 Life cycle 을 가진다.
...다음에 작성해볼 주제!