JPA 테스트 코드를 작성하다가 내가 생각하기에는 분명 쿼리가 2번 나가야 하는데 1번만 나가는 것을 발견하였다. 왜 그런지는 대충 알고 있었으나 자세히 아는게 좋을 것 같다는 생각이 들었다. 또한 이 캐시가 많은 JPA 기능들과 연결되어 있기 때문에 정리해본다.
1차 캐시는 영속성 컨텍스트(일반적으로 트랜잭션 범위) 수준에서 동작하는 캐시로 특정 세션에서 시작된 트랜잭션 내에서 엔티티를 관리하며, 그 내부에 1차 캐시가 존재한다.
엔티티 매니저가 DB에서 엔티티를 처음 로드할 때 해당 엔티티는 1차 캐시에 저장되며, 같은 트랜잭션 내에서 동일한 엔티티를 조회하는 경우 DB에 불필요한 쿼리를 보낼 필요없이 1창 캐시를 통해 성능을 최적화 할 수 있다.
void 캐시_조회() {
Member member = Member.builder()
.name("홍길동")
.age((byte) 20)
.build();
Member savedMember = memberRepository.save(member);
Member findMember = memberRepository.findById(savedMember.getId()).orElseThrow();
assertEquals(findMember, savedMember);
}
코드를 보면 JPARepository를 상속받은 MemberRepository가 member를 저장하고 해당엔티티를 1차 캐시에 저장할 것이다. 그 후 id에 해당하는 멤버를 조회하는데 이 엔티티가 이미 1차 캐시에 저장되어 있기 때문에 select 쿼리를 DB에 보내지 않고 insert 문만 보내게 된다.
또한 1차캐시는 더티 채킹 매커니즘과 연관되어 있다. 더티 태킹이란 1차 캐시에 보관된 엔티티의 상태가 변경되어 있는지를 추적하는 메커니즘이다. JPA에서 엔티티를 조회하면 1차 캐시에 조회 상태 그대로를 스냅샷을 만들어놓고 트랜잭션이 끝나는 시점에서 이 스냅샷과 비교하여다른점이 있다면 Update Query를 DB에 보낸다. 따라서 우리가 엔티티를 변경하고 해당 엔티티를 다시 save하지 않아도 JPA가 자동으로 엔티티가 변경되어 있다면 쿼리를 날려주는 것이다.
더티 체킹으로 생성되는 update 쿼리는 모든 필드를 업데이트 된다. 그러나 필드가 많은 엔티티의 일부 필드만 수정하고 싶다면 @DynamicUpdate 어노테이션을 고려해보자
JPA의 2차 캐시는 애플리케이션 수준에서 동작하는 캐시이다. 즉, 여러 세션과 여러 트랜잭션 간에 공유되는 캐시이다. 2차 캐시는 주로 읽기 전용이거나, 거의 변경되지 않는 데이터를 캐싱하는데 사용되며 저장된 엔티티는 여러 트랜잭션에서 재사용이 가능하기 때문에 데이터베이스 접근을 줄여 성능을 크게 향상시킬 수 있습니다.
그러나 2차 캐시는 동시성 제어 문제를 일으킬 수 있습니다. 따라서 적절한 캐시 전략과 동시성 제어 전략이 필요한데 주로 객체를 직접 제공하지 않고 복사본을 제공하거나 락을 이용하여 동시성을 제어하곤 한다.
2차 캐시를 사용하려면 엔티티에 javax.persistence.Cacheable 어노테이션을 사용해야 한다. @Cacheable은 true,false를 설정할 수 있는데 기본값은 true이다. 이 어노테이션을 엔티티에 추가하면 해당 엔티티는 2차 캐시를 사용한다.
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member
이렇게 @Cache 어노테이션으로 동시성 전략을 사용할 수 있는데 읽기 전용 READ_ONLY, 읽고 쓰기 READ_WRITE, 수정 작업을 거의 하지 않을 때는 NONSTRICT_READ_WRITE 등의 전략이 존재한다.
또한 2차 캐시를 이용할 때 JPA 설정을 해주어야 하는데 application.yml 파일에 2차 캐시를 활성화해주는 spring.jpa.properties.hibernate.cache.use_second_level_cache:true 설정을 해준다.cache 라이브러리인 spring-boot-starter-cache도 추가한다.
JPA에서 제공하는 캐시를 이용한다면 DB에 접근 없이 보다 더 빠르게 데이터를 제공하고 성능을 최적화할 수 있을 것 같다. 특히 2차 캐시에 대해서는 거의 모르고 있었는데 이번에 알게되어서 나중에 조회가 많이 일어나는 엔티티를 2차 캐시에 사용한다면 분명 성능적으로 발전시킬 수 있을 것 같다. JPA에서 제공하는 여러 기능을 하나씩 알아보다보니 JPA를 그냥 쿼리를 자동으로 만들어주는 라이브러리라고만 생각했던 나를 반성하게 되면서 더 Deep Learning이 필요하다고 생각한다.