1.
INSERT
1차 캐시에 User 객체가 영속화 된 후, 객체를 DB로 flush 한다.
2.
SELECT 4
1차 캐시에 id값 4의 User객체가 영속화 되어있는지 확인 한 뒤, 영속화 되어있다면 1차 캐시에서 데이터를 가져온다. 없다면 DB를 검색하여 데이터를 가져온다.
3.
UPDATE 2
1차 캐시에 id값 2의 User객체가 없다면 우선 DB에서 1차 캐시로 로드한다. 1차 캐시에서 가져온 User객체의 값을 변경 한 뒤,
a.
@Transactional 트랜잭션이 없다면
변경된 객체와 1차 캐시에 저장된 객체의 고유식별자를 비교한다. 같은 id를 가지고 있다면 나머지 변경된 데이터를 감지하고 변경된 데이터만 1차 캐시 위의 객체로 업데이트 한다. 이후 save() 메소드를 사용해 DB에 flush한다.
b.
@Transactional 트랜잭션이 있다면
컨트롤러 종료 시 커밋이 진행되는데, 변경된 데이터를 자동으로 감지한 다음, 변경된 데이터만 1차 캐시 위의 객체로 업데이트 하며, 동시에 DB로 flush 한다.
이를 더티체킹이라 하는데, 영속성 컨텍스트에 포함된 엔티티들의 변경을 감지하는 것이다.
처음 만들어진 엔티티들은 아직 영속성 컨텍스트 대상에 포함되지 않으며,
1.
트랜잭션 범위 내에서 save하거나
2.
트랜잭션 범위 내에서 저장된 엔티티를 조회해온 경우
에만 포함된다.
@Transaction이 사용되는 이유:
일단은 트랜잭션 어노테이션이 없으면 @OneToMany, @ManyToMany와 같은 레이지 로딩이 필요한 엔티티들이 정상조회되지 않습니다. JPA를 사용하다보면 부모-자식 관계(@OneToMany)를 많이 사용하는데, 이 옵션이 트랜잭션이 없으면 정상작동 하지 않습니다. LazyInitializationException 가 바로 그런 경우입니다. 그래서 OneToMany나 ManyToMany와 같이 레이지 로딩을 지원하면서 롤백 기능이 없어 성능 향상이 어느정도 되어있는 readOnly 옵션을 사용한 것입니다.
Oracle, MSSQL과 같은 READ COMMIT격리수준의 DB라 하더라도 JPA는 1차캐시를 통해 DB가 아닌 애플리케이션 레벨에서 REPEATED READ 수준을 보장해준다.
기본 격리 수준으로 REPEATABLE READ를 사용하는 DB가 아니라 다른 격리수준을 사용하는 DB나 격리수준을 변경해서 사용할 때 JPA를 사용한다면 이런 메커니즘은 숙지하고 사용하면 좋을 것 같다.
더티체킹(Dirty Checking)이란 상태 변경 검사이다.
JPA에서는 트랜잭션이 끝나는 시점에 변화가 있는 모든 엔티티 객체를 데이터베이스 반영한다. 그렇기 때문에 값을 변경한 뒤, save 하지 않더라도 DB에 반영되는 것이다.
이러한 상태 변경 검사의 대상은 영속성 컨텍스트가 관리하는 엔티티에만 적용된다.(준영속, 비영속된 객체X)
더티체킹(Dirty Checking) 원리:
•
영속성 컨텍스트란 서버와 DB사이에 존재한다.
•
JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해둔다.(일종의 스냅샷)
•
트랜잭션이 끝나고 flush할 때 스냅샷과 현재 엔티티를 비교해 변경된 엔티티를 찾아낸다.
•
JPA는 변경된 엔티티를 DB단에 반영하여 한번에 쿼리문을 날려준다.