본문 바로가기

SpringBoot

[Spring] Spring Cache와 JPA 1차 캐시 비교

반응형

개요

Spring Cache에 대한 글을 바탕으로 세미나를 진행한 적이 있는데,

다음과 같은 질문이 들어왔으나 바로 대답하지 못했었다.

jpa에서도 cache를 지원해 주는 것으로 알고 있습니다. 이때 spring cache와 jpa cache의 차이가 어떻게 되나요?

 

현재 JPA를 얕게나마 사용하는 수준이어서 기술적인 질문이 들어오면 답변을 못하는 상태라 생각됐다.

다음번에도 동일한 질문이 들어오면 답변할 수 있도록 공부하기로 하고, 정리한 내용을 본문에 작성했다.

 

https://meal-coding.tistory.com/46

 

[Spring] Cache 사용하기 (@Cacheable, @CachePut, @CacheEvict)

개요메뉴, 카테고리와 같이 자주 변경되지 않는 정적 데이터들이 있다.게다가 메뉴와 카테고리를 가져오기 위해서 DB를 조회하는 빈도수도 상당히 높은 걸 알 수 있다. 데이터 양이 적다면 유야

meal-coding.tistory.com


내용

#1 질문

jpa에서도 cache를 지원해 주는 것으로 알고 있습니다. 이때 spring cache와 jpa cache의 차이가 어떻게 되나요?

 


#2 답변

Spring Cache와 JPA 1차 캐시의 생명주기가 서로 달라 동작에서 차이가 있다.

 

JPA 1차 캐시

  • Transaction 단위에서 cache를 저장한다.
  • 한 Transaction 내(영속성 컨텍스트)에서 동일한 데이터를 요청하게 될 경우, 저장된 cache를 반환한다.

Spring Cache

  • 애플리케이션 또는 지정된 범위(메모리/Redis)에서 cache를 저장한다.
  • 애플리케이션이 동작하고 있을 때 동일한 데이터를 요청하게 될 경우, 저장된 cache를 반환한다.
  • 지정된 범위(메모리/Redis 등)를 확인했을 때 동일한 데이터 요청이 확인되면, 저장된 cache를 반환한다.

위의 내용과 추가적인 차이를 표로 정리하면 아래와 같다.

  JPA 1차 캐시 Spring Cache
캐시 저장소 Transaction 내부(영속성 컨텍스트) 애플리케이션 또는 외부 저장소 (메모리, Redis 등)
생명주기 Transaction 동안 유지 TTL 또는 앱 수명 기반, 명시적 제거 가능
적용 대상 Entity (PK 기반) 메소드 실행 결과 (Key-Value)
캐시 범위 (또는 작동 범위) 동일 Transaction 내 전체 애플리케이션 범위

 


#3 예제 및 log

아래와 같은 코드를 작성하고 실제 log를 찍어 보겠다.

 

예제 코드
@Service
class CategoryService(
  private val categoryRepository: CategoryRepository,
  private val categoryMapper: CategoryMapper
) {
  @Cacheable(value = ["Category"], key = "#id")
  fun getOneCategoryWithSpringCache(id: Int): CategoryDto {
      logger.debug("[DB 접근 - Spring Cache]")
      val categoryEntity = categoryRepository.findById(id).orElseThrow()
      return categoryMapper.toDto(categoryEntity)
  }

  @Transactional(readOnly = true)
  fun getOneCategoryWithJpaCache(id: Int): CategoryDto {
    logger.debug("1차 호출 - JPA")
    val category1 = categoryRepository.findById(id).orElseThrow()
    logger.debug("2차 호출 - JPA")
    val category2 = categoryRepository.findById(id).orElseThrow()

    logger.debug("두 객체 동일성 비교: ${category1 === category2}")
    return categoryMapper.toDto(category1)
  }

  @Cacheable(value = ["Category"], key = "#id")
  @Transactional(readOnly = true)
  fun getOneCategoryUsingBoth(id: Int): CategoryDto {
    logger.debug("[DB 접근 - Spring Cache]")
    logger.debug("1차 호출 - JPA")
    val category1 = categoryRepository.findById(id).orElseThrow()
    logger.debug("2차 호출 - JPA")
    val category2 = categoryRepository.findById(id).orElseThrow()

    logger.debug("두 객체 동일성 비교: ${category1 === category2}")
    return categoryMapper.toDto(category1)
	}
}

 

Spring Cache log

JPA 1차 캐시 log

Spring Cache & JPA 1차 캐시 log

 

log 분석
▶ Spring Cache
  - 첫 호출: [DB 접근 - Spring Cache], JPA 호출 로그 출력됨
  - 두 번째 호출: 캐시된 데이터 반환 → 메서드 자체 실행 안 됨 → 로그 출력되지 않음

▶ JPA 1차 캐시
  - 같은 트랜잭션 내에서 두 번 `findById` 호출해도 로그는 출력됨 (메서드는 실행되었기 때문)
  - 하지만 2번째 쿼리는 실행되지 않음 (1차 캐시로부터 조회)

▶ Spring Cache + JPA 1차 캐시
  - 첫 호출: 메서드 실행됨 → JPA 1차 캐시 사용 → 쿼리는 1번만 수행
  - 두 번째 호출: Spring Cache가 가로채서 메서드 호출 자체가 안 됨 → 내부 로그 출력 안 됨

정리

log를 분석하며 내린 결론은 다음과 같다.

  • 동일 트랜잭션 내 다중 조회라면 JPA 1차 캐시만으로 충분
  • 서비스 전반에서 자주 조회되는 값은 Spring Cache를 활용해 DB 접근 자체를 줄이는 것이 효과적
반응형