Spring boot - Cache

2025. 1. 7. 20:18·Spring/SpringBoot

스프링 부트 Cache

ConcurrentMapCacheManager

스프링 부트에서는 @EnableCaching 사용 시, 인메모리 캐싱이 활성화 된다.
이를 이용해 어플리케이션 내에 ConCurrentHashMap을 활용한 캐싱이 간단하게 이루어진다.

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    CacheManager cacheManager() {
        return new ConcurrentMapCacheManager();
    }
}

다만,

    1. 캐싱되는 데이터의 유효기간을 설정하지 못한다는 단점.
    1. 다른 어플리케이션과 캐싱 데이터를 공유하지 못한다는 단점.

두 가지가 크게 작용한다.

RedisCacheManager

@Configuration
@EnableCaching
public class CacheConfig {

    @Autowired
    RedisProperties redisProperties;

    @Bean
    RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(
               redisProperties.getHost(),
               redisProperties.getPort()
        );
    }

    RedisCacheConfiguration redisCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(60L))
                .serializeKeysWith(SerializationPair.fromSerializer(RedisSerializer.string()))
                .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues();
    }

    @Bean
    CacheManager cacheManager() {
        return RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory())
                .cacheDefaults(redisCacheConfiguration())
                .build();
    }
}

위와 같이 레디스를 사용하며, 캐싱에 적용될 규칙들을 더 엄밀히 사용할 수 있게 된다.

다음으로, 어플리케이션 레벨에서 이를 활용하는 방안을 살펴보자

캐시 사용 방법

캐시에는 CacheName, Cache Key 라는 개념이 존재한다.

CacheName 은 해당 데이터가 저장될 캐시 저장소를 가리키며,
CahceKey는 지정된 저장소에 Key-Value로 저장되는 상황에서의 Key를 지정한다.

@CacheConfig

@CacheConfig 는 클래스레벨 내 메소드'들'에 적용될 범용적인 설정을 할 수 있도록 한다.

@Cacheable은 캐시가 존재할 시, 해당 메소드를 호출하지 않고, 값을 캐시에서 불러온다.
@CachePut 은 메소드를 무조건적으로 실행하며, 반환된 값을 캐시에 적재하고 이를 Caller에 응답한다.

메소드의 리턴 값으로 캐시가 업데이트 된다는 점을 유의해야한다. 

@CacheEvict는 메소드를 무조건적으로 실행하며, 캐시에서 해당 id로 적재된 데이터를 삭제한다.
@Caching은 메소드내에 위의 여러 캐싱을 적용할 수 있도록 허용한다.

캐시 사용 사례

캐싱을 적용하기 위해 다음과 같은 컨트롤러를 구성했다.

@RestController
@RequiredArgsConstructor
public class MemberController {
    private final MemberService memberService;

    @PostMapping
    public ResponseEntity<Long> addRandomUser() {
        return ResponseEntity.ok(memberService.addMember());
    }

    @GetMapping("/all")
    public ResponseEntity<List<Member>> loadAll() {
        return ResponseEntity.ok(memberService.loadAll());
    }

    @GetMapping("/{id}")
    public ResponseEntity<Member> addRandomUser(
            @PathVariable final Long id
    ) {
        return ResponseEntity.ok(memberService.getDetail(id));
    }

    @PutMapping("/{id}/{name}")
    public ResponseEntity<Member> modifyName(
            @PathVariable final Long id,
            @PathVariable final String name
    ) {
        return ResponseEntity.ok(memberService.changeName(id, name));
    }

    @DeleteMapping("{id}")
    public ResponseEntity<Member> deleteMember(
            @PathVariable final Long id
    ) {
        memberService.delete(id);
        return ResponseEntity.ok().build();
    }
}

마찬가지로, 이와 대응되는 Service 객체를 구성한다.

@Service
@RequiredArgsConstructor
@Transactional
@CacheConfig(cacheNames = "memberCache")
public class MemberService {
    private final MemberRepository memberRepository;

    @CacheEvict(key = "'all'")
    public Long addMember() {
        return memberRepository.save(new Member(new Random().toString())).getId();
    }

    @Transactional(readOnly = true)
    @Cacheable(key = "'all'")
    public List<Member> loadAll() {
        return memberRepository.findAll();
    }

    @Cacheable(key = "#id")
    @Transactional(readOnly = true)
    public Member getDetail(final Long id) {
        return memberRepository.findById(id).orElseThrow();
    }

    @CacheEvict(allEntries = true)
    public void delete(final Long id) {
        memberRepository.deleteById(id);
    }

    @CachePut(key = "#id")
    public Member changeName(final Long id, final String name) {
        final Member member = memberRepository.findById(id).orElseThrow();
        member.setName(name);
        return member;
    }
}

@CacheEvict(allEntries = true) 가 눈에 띈다.
이 어노테이션은 addUser, delete 메소드에 적용되어 있다.
해당 어노테이션은 키와 관계 없이 해당 캐시 저장소의 모든 데이터를 삭제하라는 의미이며, 이를 통해 loadAll과 같은 메소드에서 데이터 오류를 피할 수 있다.

하지만, 테이블의 행이 수천에서 수만개가 되는 상황에서 전체 행을 불러오는 쿼리는 드뭄으로, 이런 쿼리를 캐싱하는 것은 메모리낭비를 유발한다고 볼 수 있다.

changeName을 보면, 실제 DB에 대한 레코드 수정과 캐싱된 데이터를 모두 수정하고 있지만,
이는 loadAll에서 리턴되는 응답값을 수정하지 못하고 있다.

이를 수정하기 위해

    @Caching(
            put = @CachePut(key = "#id"),
            evict = @CacheEvict("'all'")
    )

와 같이 바꿔줄 수도 있겠다.

또는, 직접 CacheManager를 주입 받아 적용할 수 있겠지만, 이는 비즈니스 로직을 오염시키는 상황을 초래한다.
이럴빠엔 AOP를 왜 써?

캐싱 활용 - 페이징에서의 캐싱

하지만, 페이징에는 충분히 응용될 수 있다.

페이징에 대한 캐싱은 다음 포스트에서 다루겠다.

'Spring > SpringBoot' 카테고리의 다른 글

Spring boot - @Transactional 전파 주의 사항  (0) 2025.01.13
Spring boot - Cache (2) 페이지 조회 캐싱  (0) 2025.01.08
[SpringBoot] 스프링의 에러처리 탐구  (0) 2024.01.17
[Spring] Spring Security 인증 구성  (0) 2024.01.02
[Spring] 다수의 SecurityFilterChain 구성 방법  (0) 2024.01.01
'Spring/SpringBoot' 카테고리의 다른 글
  • Spring boot - @Transactional 전파 주의 사항
  • Spring boot - Cache (2) 페이지 조회 캐싱
  • [SpringBoot] 스프링의 에러처리 탐구
  • [Spring] Spring Security 인증 구성
윤희종
윤희종
호기심을 잃지 말자 지적, 질문은 언제나 환영합니다 ;)
  • 윤희종
    서버견문록
    윤희종
  • 전체
    오늘
    어제
    • 분류 전체보기 (36)
      • 데일리 플랜 (1)
      • 이것저것 (4)
      • Java (6)
      • Spring (12)
        • SpringBoot (10)
        • Spring MVC (0)
      • Computer Science (4)
        • Network (1)
        • Operating System (0)
        • Data Structure (0)
        • Algorithm (2)
        • Database (0)
      • IOS (0)
      • 프로그래머스 문제풀이 (2)
      • 프로젝트 일기 (7)
        • 한편의 수학 학원 (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    springboot
    성능 개선
    제네릭
    스프링 시큐리티 구성
    Spring
    비동기 처리 유의점
    mysql 쿼리 최적화
    SecurityFilterChain 구성
    인증 우회 테스트
    알고리즘
    read through
    캐시
    스프링 부트 인증 우회
    SecurityFilterChain
    cache write back
    스프링
    스프링 부트
    스프링 시큐리티 사용법
    인증 테스트
    servlet
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
윤희종
Spring boot - Cache
상단으로

티스토리툴바