본문 바로가기

책 읽기/스프링 책 읽기

스프링 책 읽기(Spring in action) - 13. 데이터 캐싱하기

캐싱(caching) - 자주 필요한 정보를  저장하는 방법으로 해당 정보가 필요할 때 사용될 수 있게 함, 스프링은 캐시솔루션을 구현하지 않지만, 다양한 캐싱 구현체와 통합된 캐싱을 위한 선언적 지원을 제공

 


캐시 지원하기

스프링의 캐시 추상화 지원 ->  애너테이션 주도 캐싱 or XML 선언 캐싱(생략)

 @Caheable, @CacheEvict : 해당 애너테이션을 사용하여, 메소드를 애너테이션하는 것이 가장 일반적인 방법

* 빈의 캐싱 애너테이션 적용 전에는, 스프링의 애너테이션 주도 캐싱 지원을 사용해야함 

  1. @Configuration
  2. @EnableCaching // 캐싱 활성화
  3. public class CachingConfig {
  4. @Bean
  5. public CacheManager cacheManager() {
  6. return new ConcurrentMapCacheManager(); // 캐시 매니저 선언
  7. }
  8. }

@EnableCaching : 스프링의 캐싱 애너테이션을 하는 포인트커트를 가지는 애스펙트 생성... 캐시 상태와 애너테이션에 따라 애스펙트는 캐시의 값을 가져오거나 추가하거나 삭제한다.

ConcurrentMapCacheManager : 간단한 캐시 매니저. 저장소로 ConcurrentHashMap 사용. 캐시 저장소는 메모리 기반이며 애플리케이션 라이프사이클에 얽혀있기 때문에, 개발이나 작은규모에서 적합하다

 


캐시 매니저 설정하기

스프링 3.2이상에서는, 7개의 캐시매니저 구현체를 지원

  • SimpleCacheManager
  • NoOpCacheManager
  • ConcurrentMapCacheManager
  • CompositeCacheManager
  • EhCacheCacheManager
  • RedisCacheManager(스프링 Redis)
  • GemfireCacheManager(스프링 GemFire)

 


EhCache로 캐싱하기

Ehcache - 가장 인기 있는 캐시 프로바이더 중 하나 - 스프링은 EhCacheCacheManager를 통해 통합된 EhCache 매니저를 제공한다 -> ehcache 특징 : https://sjh836.tistory.com/129

  1. @Configuration
  2. @EnableCaching
  3. public class CachingConfig {
  4. @Bean
  5. public EhCacheCacheManager cacheManager(CacheManager cm) { // EhCacheCacheManager 설정
  6. return new EhCacheCacheManager(cm);
  7. }
  8. @Bean
  9. public EhCacheManagerFactoryBean ehcache() { // EhCacheManagerFactoryBean
  10. EhCacheManagerFactoryBean ehCacheFactoryBean = new EhCacheManagerFactoryBean();
  11. ehCacheFactoryBean.setConfigLocation(new ClassPathResource("com/bobfull/cafe/cache/ehcache.xml"));
  12. return ehCacheFactoryBean;
  13. }
  14. }

* setConfiguration() 메소드를 통해, Ehcache 설정이 된 xml파일을 가져온다.

  1. <ehcache>
  2. <cache name="cafeCache"
  3. maxBytesLocalHeap="50m"
  4. timeToLiveSeconds="100">
  5. </cache>
  6. </ehcache>
  7. ## 50MB의 100초 유효기간을 가진 캐시저장소

 


Redis로 캐싱하기

캐시 엔트리에서는 키-값 이외에는 다른 값을 가지지 않음. 따라서 키-값 저장소인 Redis가 적합할 수 있다.

스프링 데이터 Redis에는 RedisCacheManager를 제공한다. RedisCacheManagerRedisTemplate를 통해 Redis 서버를 가지고 동작 => RedisCacheManager를 위해서는, 각각 RedisTemplate과, RedisConnectionFactory의 구현 빈이 필요(12장)

  1. @Configuration
  2. @EnableCaching
  3. public class CachingConfig {
  4. @Bean // RedisCacheManager 빈
  5. public CacheManager cacheManager(RedisTemplate redisTemplate) {
  6. return new RedisCacheManager(redisTemplate); // Redis템플릿의 인스턴스를 생성자에 전달하여, 생성
  7. }
  8. @Bean // RedisConnectionFactory 빈
  9. public JedisConnectionFactory redisConnectionFactory() {
  10. JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
  11. jedisConnectionFactory.afterPropertiesSet();
  12. return jedisConnectionFactory;
  13. }
  14. @Bean // RedisTemplate 빈
  15. public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisCF) {
  16. RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
  17. redisTemplate.setConnectionFactory(redisCF);
  18. redisTemplate.afterPropertiesSet();
  19. return redisTemplate;
  20. } }

 


다중 캐시 매니저 사용

CompositeCacheManager : 한 개 이상의 캐시매니저를 선택해야할 때 사용

  1. @Bean
  2. public CacheManager cacheManager(net.sf.ehcache.CacheManager cm, javax.cache.CacheManager jcm) {
  3. CompositeCacheManager cacheManager = new CompositeCacheManager();
  4. List<CacheManager> managers = new ArrayList<CacheManager>();
  5. managers.add(new JCacheCacheManager(jcm));
  6. managers.add(new EhCacheCacheManager(cm))
  7. managers.add(new RedisCacheManager(redisTemplate()));
  8. cacheManager.setCacheManagers(managers); // 개별 캐시 매니저 추가
  9. return cacheManager;
  10. }

* JCache구현체를 체크로 시작, 그 다음은 EhCache.. 최종적으로 캐시 엔트리를 위해 Redis 동작을 체크

* 3개의 캐시매니저를 사용하여 설정

 


캐시 어노테이션 메소드

캐시 어노테이션으로, 애스펙트를 생성해서 동작한다.

 


캐시 채우기

@Cacheable, @CachePut는 둘다 캐시를 채우기 위한 어노테이션이지만, 각각 동작하는 방식이 다르다

@Cacheable : 먼저 캐시에서 항목을 찾고 일치하는 항목을 찾으면 메소드 호출을 우선. 일치하는 항목이 없으면 메소드가 호출되고 리턴 된 값이 캐시에 저장

@CachePut캐시에서 일치하는 값을 확인하지 않고 항상 대상 메소드를 호출 할 수 있도록하며 리턴 된 값을 캐시에 추가

@Cacheable, @CachePut이 공통으로 사용하는 어트리뷰트

  1. @Cacheable("spittleCache")
  2. public Spittle findOne(long id) {
  3. try {
  4. return jdbcTemplate.queryForObject(
  5. SELECT_SPITTLE_BY_ID,
  6. new SpittleRowMapper(),
  7. id);
  8. } catch (EmptyResultDataAccessException e) {
  9. return null;
  10. }
  11. }

* findeOne()호출 시, 캐싱 애스펙트가 호출을 가로챈 후, "spittleCache"라는 캐시에 반환된 값을, id라는 매개변수를 키로 찾음. 값이 있으면 캐시에 저장된 값을 반환한 후, 메소드는 호출하지 않음

* 위 코드는 구현 메소드에, 어노테이션을 달았기 때문에 해당 메소드만 캐싱을 하지만, 만약 인터페이스에 어노테이션을 한다면, 모든 구현 메소드에 적용될 것

 


캐시에 값 넣기

@CachePut의 경우에는, 메소드가 항상 호출되고, 해당 반환 값이 캐시에 저장된다. 그렇기 때문에, save() 메소드 등, 저장을 할 때에 매우 용이하다(저장과 동시에 캐시가 저장되므로)

  1. @CachePut("spittleCache")
  2. Spittle save(Spittle spittle);

-> 이런식으로 @CachePut으로 save()메소드를 어노테이션 할 수 있다. 그러나 @Cacheable과 달리, 이런 경우에 캐시 키가 Spittle이기 때문에, 모든 Spiittle이 키가 되면서, Key - Spittle, Value - Spittle인 이상한 상황 발생.. 이런 점을 해결하기 위해 맞춤형 캐시 키가 있음

 


맞춤형 캐시 키 만들기

@Cacheablue, @CachePut 모두, 기본 키를 SpEL을 통해서, 키를 바꿔줄 수 있음. 스프링은 캐싱을 위한 SpEL을 작성할 때에 유용한 메타데이터를 가지고 있음

  1. @CachePut(value="spittleCache", key="#result.id")
  2. Spittle save(Spittle spittle);

* 위의 표현식에서 @CachePut은 #result를 key를 지정해 줄 수 있음

* key: id // Value : Spittle

 


조건 캐싱

캐시를 사용하다보면, 어떤 데이터의 경우에는 캐시를  사용하지 않으려 할 수 있다.

스프링에서는 이런 점을 고려하여, SpEL을 이용하여 조건부 캐싱을 할 수 있음(unless, conditon 사용)

unless - 캐싱을 하지 않는 조건

  1. @Cacheable(value="spittleCache" unless="#result.message.contains('NoCache')")
  2. Spittle findOne(long id);

=> 위의 SpEL에서 있던 #result 사용.. -> message 내에 NoCache가 있는 것을 제외한다

conditon - 캐싱을 하는 조건

  1. @Cacheable(value="spittleCache" unless="#result.message.contains('NoCache')" condition="#id >= 10")
  2. Spittle findOne(long id);

=> #id 가 10 이상일 때만 캐싱(unless의 조건도 포함)

 


캐시 엔트리 삭제하기

@CacheEvict : 캐시를 제거하는 어노테이션 주로 remove() 등, 제거 메소드 등에서 사용하면 좋음(void에서 사용 가능)

  1. @CacheEvict("spittleCache")
  2. void remove(long spittleId);

* spittleId에 맞는, 캐시가 삭제됨

==> @CacheEvict 어노테이션에서 사용가능한 어트리뷰트(unless는 제공하지 않음)