본문 바로가기

Spring/JPA

[JPA] 영속성 컨텍스트(2) - 영속성 컨텍스트의 엔티티 관리

728x90
반응형

 

인프런에서 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편을 참고하여 작성한 글입니다.

 

 

 

JPA에서의 1차 캐시

 

JPA는 어플리케이션과 DB 사이의 중간 역할을 하기 때문에 캐싱 기능을 사용할 수 있습니다.

1차 캐시는 영속성 컨텍스트 안에 있으며, 영속 상태의 엔티티는 1차 캐시에 들어감으로써 캐싱 기능을 사용할 수 있게 됩니다.

 

1차 캐시는 여러개의 엔티티가 들어갈 수 있기 때문에, 각 엔티티를 구별할 수 있는 @Id와 Entity가 함께 들어갑니다.

즉 엔티티의 PK라 할 수 있는 Id로 1차 캐시 안에서 엔티티들을 구분하게 됩니다.

 

1차 캐시
@Id Entity
1 PK가 1인 엔티티 객체
2 PK가 2인 엔티티 객체
... ...

 

 

먼저 Entity Manager가 생성한 entity를 persist하여 영속상태로 만들면 해당 엔티티가 1차 캐시에 들어가게 됩니다.

이후에 만약 이 엔티티의 조회가 일어난다면 DB에서 엔티티 정보를 가져오지 않고 1차 캐시에서 해당 엔티티의 @Id를 조회하여 Entity를 꺼내게 됩니다.

 

 

그렇다면 어플리케이션에서 영속하지 않은 엔티티를 조회하고자 하면 어떻게 될까요?

당연히 1차 캐시에 해당 엔티티가 없음을 확인하고 난 후에 DB에서 데이터를 조회하게 됩니다.

조회한 데이터는 1차 캐시에 저장되어 영속 상태가 된 후, 조회를 요청한 곳에 이 정보를 반환해줍니다.

 

 

 

commit과 flush

 

JPA에는 트랜잭션에 대한 commit 기능과 flush 기능이 있습니다.

JPA는 영속성 컨텍스트에서 엔티티들의 데이터를 관리해준 다음에 특정 시점이 되면 이를 DB에 반영해야 합니다.

이 때 DB에 데이터를 반영하는 방법으로 flush와 commit 이 있습니다.

쓰기 지연 SQL 저장소에는 등록된 변경이 감지된 엔티티나 등록, 수정, 삭제 등의 쿼리가 등록되는데,

이 SQL을 DB에 직접 호출하여 날리는 것이 flush입니다.

commit은 flush를 자동으로 호출하고, 트랜잭션까지 커밋하는 역할을 합니다.

flush는 커밋이 아닌 단순히 DB와 동기화를 맞추는 작업이기 때문에 1차 캐시가 비워지지 않고,

트랜잭션 또한 종료되지 않습니다.

 

 

 

JPA 캐싱의 특징

 

동일성 보장

엔티티를 영속시킴으로써 다음과 같은 비교가 가능해집니다.

 

EntityA e1 = entityManager.find(EntityA.class, "1");
EntityA e2 = entityManager.find(EntityA.class, "1");
// -> e1 == e2 는 true

 

같은 1차 캐시에서 PK가 1번인 엔티티를 찾았기 때문에 e1과 e2는 같은 객체임이 보장됩니다.

이에 따라서 REAPEATABLE READ 등급의 트랜잭션 격리 수준을 어플리케이션 수준에서도 제공할 수 있게 됩니다.

 

 

- REAPEATABLE READ? 트랜잭션 격리 수준?
트랜잭션 격리 수준이란 여러개의 트랜잭션이 처리될 때에, 어떠한 트랜잭션이 다른 트랜잭션에서 변경 및 조회하는 데이터를 확인할 수 있도록 허용할지에 대한 수준 제한입니다.
격리 수준이 높아질수록(더 많이 제한할수록) 다른 트랜잭션의 처리를 기다려야 하기 때문에 동시성이 떨어지지만, 더 안전해집니다.
트랜잭션 격리 수준은 0~3단계로 이루어져있는데, 그 중에 Repeatable Read는 레벨 2에 속합니다.
이는 트랜잭션이 완료될 때까지 SELECT 문장이 사용되는 모든 데이터에 락이 걸리는 것으로, 이러한 격리를 통해 한번 SELECT한 데이터를 다시 SELECT해도 결과는 같음을 보장해줍니다.

 

 

쓰기 지연

엔티티 매니저를 만들고 난 뒤에는 transaction.begin()으로 트랜잭션을 시작합니다.

그리고 그 사이에 데이터를 삽입하거나, 수정하거나 조회하는 등의 작업을 하게 됩니다.

이 때 하는 작업들은 아직 커밋이 되지 않은 상태이기 때문에 DB에 반영되지 않습니다.

즉, 내가 시작한 트랜잭션을 커밋(transaction.commit())하지 않으면 데이터베이스에 SQL문이 날라가지 않는 쓰기 지연이 발생합니다.

 

이 쓰기 지연은 트랜잭션을 지원하기 때문에, 직접 SQL을 다룰때 DB에 락이 걸려 딜레이가 되는 시간을 줄여줍니다.

즉, 필요한 작업들을 모두 모아 한번에 DB에 SQL을 보내고 트랜잭션을 커밋해버리기 때문에 다른 비즈니스 로직에서 데이터베이스의 락이 풀릴때까지 기다리지 않아도 됩니다.

 

 

변경 감지

JPA의 1차 캐시에는 스냅샷이라는 것이 존재합니다.

스냅샷은 DB에서 해당 엔티티의 정보를 읽었을 때의 최초의 상태와 같이 제일 처음에 영속성 컨텍스트에 들어왔을 때의 상태를 저장하는 곳입니다.

이는 1차 캐시에 저장된 실제 엔티티와 다를 수도 있고, 같을 수도 있습니다.

 

예를 들어 DB에서 가져온 데이터를 어플리케이션에서 수정하는 등의 작업을 하게 되면 스냅샷과 달라지게 됩니다.

따라서 JPA는 이 스냅샷에 있는 데이터와 비교하여 변경이 일어났음을 확인하면 쓰기 지연 SQL 저장소에 업데이트 쿼리를 저장합니다.

이 쿼리들은 flush의 직접 호출이나 commit을 통해서 DB에 반영됩니다.

 

 

 

 

 

728x90
반응형