16장 트랜잭션 과 락, 2차캐시
트랜잭션, 락
ACID 원칙
Atomcity (원자성) : 트랜잭션은 일련의 작업들이 하나인것 처럼 전부 성공 하거나 전부실패해야한다.
Consistency (일관성) : 트랜잭션은 일관성있는 DB를 유지해야한다.
Isolaition (격리성) : 트랜잭션의 작업중엔 다른 트랜잭션이 영향을 줄수없다.
Druability (지속성) : 트랜잭션의 결과는 항상 지속되어야한다.
1차 캐시
1차 캐시는 우리가 알고있듯이 영속성 컨텍스트 내부에 보관된다.
기본전략으로 트랜잭션과 동일한 생명주기를 가지고있다.
엔티티를 조회할때 먼저 1차 캐시를 조회한 뒤 엔티티가 없다면 디비에 접근하는 방식이다.
OSIV 를 사용하면 트랜잭션이 끝나도 유효하다.
JPQL 사용 시 원래 동작처럼 영속성 컨텍스트 부터 확인하지 않고 DB에 접근해서 엔티티를 가져온뒤
1차캐시에 엔티티가 존재하면 DB에서 가져온 엔티티는 삭제하는 방식이다.
2차캐시

2차 캐시는 1차 캐시 와는 다르게 애플리케이션 전 범위가 생명주기이다.
2차 캐시 동작
1차 캐시 와 동일하게 엔티티 조회시 2차 캐시를 먼저 확인 후 있다면 DB 접근을 하지않고 엔티티의 결과를 복사해서 반환한다.
여기서 중요점은 객체를 그대로 반환하는것이 아닌 복사본을 반환한다는 것이다.
한 객체를 그대로 게속해서 반환하게 되면 동시성 문제가 발생하는데 복사본을 반환 하므로 동시성 문제를 피해갈수있다.
1차 캐시에서 동시성 문제
A라는 객체를 조회해서 1차 캐시에서 가져왔다. 해당 객체를 수정 한 뒤 findall()을 JPQL 을 통한 벌크실연산을 진행시켰다. 하지만 조회해보니 수정되기 전 이름이 조회되는걸 볼수있다.
DB에는 수정된 이름이 저장되있는 상태이다. 왜 이런 결과가 나왔을까?
JPQL 은 캐시 접근 없이 DB로 직행한다. 그러고나서 캐시를 확인하는데 캐시에는 아직 수정 전 이름을 가진 객체를 가지고 있고 해당 엔티티가 존재하므로 DB에서 가져온 데이터를 버리고 캐시에 있는 객체를 취한다. 결국은 수정 되기 전 이름을 가진 객체를 불러온것이다.
@Modifying의 clearAutomatically 를 사용하면 DB와 영속성 컨텍스트의 동기화를 도와줘서 해당 오류가 발생하지 않는다.
고로 2차 캐시는 동시성 문제에서 벗어나고 애플리케이션 전 지점을 생명주기를 가지고 있다는 점에서 잘만 사용하면 애플리케이션 조회 성능을 극대화 할수있다.
사용 방법
엔티티 위에 @Cacheable 를 적어주면 된다.
그리고 application.yml 에 아래와 같이 적어주면 된다.
spring.jpa.properties.hibernate.cache.use_second_level_cache = true
// 2차 캐시 활성화합니다.
spring.jpa.properties.hibernate.cache.region.factory_class
// 2차 캐시를 처리할 클래스를 지정합니다.
spring.jpa.properties.hibernate.generate_statistics = true
// 하이버네이트가 여러 통계정보를 출력하게 해주는데 캐시 적용 여부를 확인할 수 있습니다.속성
usage : CacheConcurrencyStrategy를 사용해서 캐시 동시성 전략을 설정합니다.
region : 캐시 지역을 설정합니다.
include : 연관 객체를 캐시에 포함할지 선택합니다. all, non-lazy 옵션을 선택할 수 있습니다. 기본값은 all 입니다.
CacheConcurrencyStrategy
READ_ONLY : 자주 조회하고 수정 작업을 하지 않는 데이터에 적합합니다.
READ_WRITE : 조회 및 수정 작업을 하는 데이터에 적합합니다. Phantom Read 가 발생할 수 있으므로 SERIALIZABLE 격리 수준에서는 사용할 수 없습니다.
NONSTRICT_READ_WRITE : 거의 수정 작업을 하지 않는 데이터에 적합합니다.
Last updated