주요 콘텐츠로 건너뛰기

CooLWJ 검색

选择搜索范围,输入关键词后按回车。

언어

Spring Cache 사용 소개

일 SpringCache 기반 캐싱 방안

1.1 SpringCache 소개

springcache 는 spring3.1 버전에서 출시되었으며, 캐시 사용을 캡슐화하고 추상화한다. 메서드에 annotation을 사용하면 캐시된 결과를 얻을 수 있다. 바로 annotation을 사용하기 때문에 비즈니스 코드와 캐시 코드의 결합도 문제를 해결한다. 즉, 비즈니스 코드를 변경하지 않고도 기존 코드가 즉시 캐시를 지원하도록 하여, 개발자가 캐시를 의식하지 않고 사용할 수 있게 한다.

1.2 SpringCache 개요

캐시 프레임워크는 Redis、Memcached、Guava、Caffeine 등 매우 다양하며,각각의 장점이 있습니다.
프로그램에서 캐시를 사용하려면 이러한 프레임워크와 결합해야 합니다. 현명한 아키텍트들은 이미 인터페이스를 활용해 결합도를 낮추고 있습니다. 객체 지향의 추상화와 다형성을 활용하여 비즈니스 코드와 구체적인 프레임워크를 분리하는 것입니다.
하지만 여전히 코드에서 캐시 관련 인터페이스와 메서드를 명시적으로 호출하여 적절한 시점에 데이터를 캐시에 넣고, 적절한 시점에 캐시에서 데이터를 읽어야 합니다. 한번 생각해 보세요「AOP」의 적용 상황이라면, 애초에 AOP가 맡아야 할 일이 아닌가?
Spring 3.1부터 [@Transactional](/Transactional ) 애너테이션을 통한 트랜잭션 지원과 유사한 애너테이션 기반 캐시 지원을 제공하며, 캐시 추상화도 제공한다. 그전에는 일반적으로 AOP를 통해 구현했다.
Spring Cache 사용의 이점:

  • 기본적인 Cache 추상화를 제공하여, 다양한 하위 Cache 구현으로 쉽게 전환할 수 있게 한다;

  • Cache 애너테이션을 사용하면 트랜잭션과 비슷하게 캐시 로직을 비즈니스 코드에 투명하게 적용할 수 있으며, 더 적은 코드만으로 구현할 수 있다;

  • 트랜잭션 롤백 시 캐시도 자동으로 롤백하는 기능을 제공합니다;

  • 비교적 복잡한 캐시 로직을 지원한다;

Spring Cache 기술 표식

1.3 Spring Cache 핵심 설계

Spring 3.1부터 Spring의 캐시 추상화인 Spring Cache가 도입되었다. 다음을 정의하여 org.springframework.cache.Cache 및 org.springframework.cache.CacheManager 인터페이스로 서로 다른 캐시 기술을 통합하고, JCache 애너테이션을 사용해 개발 과정을 간소화할 수 있도록 지원합니다.
Cache 인터페이스는 캐시 구성 요소의 규격을 정의하며, 캐시의 다양한 연산을 포함한다. Spring은 Cache 인터페이스에 대해 다양한 xxxCache 의 구현: RedisCache , EhCacheCache , ConcurrentMapCache 등.
일부 소스 코드를 통해 Cache 인터페이스와 CacheManager 인터페이스.

Cache 인터페이스

public interface Cache {     //Cache名称     String getName();      //Cache负责缓存的对象     Object getNativeCache();      /**      * 获取key对应的ValueWrapper      * 没有对应的key,则返回null      * key对应的value是null,则返回null对应的ValueWrapper      */     @Nullable     Cache.ValueWrapper get(Object key);      //返回key对应type类型的value     @Nullable     <T> T get(Object key, @Nullable Class<T> type);      //返回key对应的value,没有则缓存Callable::call,并返回     @Nullable     <T> T get(Object key, Callable<T> valueLoader);      //缓存目标key-value(替换旧值),不保证实时性     void put(Object key, @Nullable Object value);      //插入缓存,并返回该key对应的value;先调用get,不存在则用put实现     @Nullable     default Cache.ValueWrapper putIfAbsent(Object key, @Nullable Object value) {         Cache.ValueWrapper existingValue = this.get(key);         if (existingValue == null) {             this.put(key, value);         }          return existingValue;     }      //删除缓存,不保证实时性     void evict(Object key);      //立即删除缓存:返回false表示剔除前不存在制定key活不确定是否存在;返回true,表示该key之前存在     default boolean evictIfPresent(Object key) {         this.evict(key);         return false;     }      //清除所有缓存,不保证实时性     void clear();      //立即清楚所有缓存,返回false表示清除前没有缓存或不能确定是否有;返回true表示清除前有缓存     default boolean invalidate() {         this.clear();         return false;     }      public static class ValueRetrievalException extends RuntimeException {         @Nullable         private final Object key;          public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) {             super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);             this.key = key;         }          @Nullable         public Object getKey() {             return this.key;         }     }      //缓存值的一个包装器接口,实现类为SimpleValueWrapper     @FunctionalInterface     public interface ValueWrapper {         @Nullable         Object get();     } }

이처럼 Cache 인터페이스는 캐시의 get put evict 등 관련 연산을 추상화한다.

AbstractValueAdaptingCache 추상 클래스

public abstract class AbstractValueAdaptingCache implements Cache {      //是否允许Null值     private final boolean allowNullValues;      protected AbstractValueAdaptingCache(boolean allowNullValues) {         this.allowNullValues = allowNullValues;     }      public final boolean isAllowNullValues() {         return this.allowNullValues;     }      @Nullable     public ValueWrapper get(Object key) {         return this.toValueWrapper(this.lookup(key));     }      @Nullable     public <T> T get(Object key, @Nullable Class<T> type) {         //查询到的缓存值做fromStoreValue转换         Object value = this.fromStoreValue(this.lookup(key));         //转换后非null值且无法转换为type类型则抛出异常         if (value != null && type != null && !type.isInstance(value)) {             throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);         } else {             return value;         }     }      //从缓存中获取key对应的value,子类实现     @Nullable     protected abstract Object lookup(Object key);      //对于从缓存中获取的值,允许为空且值为NullValue时,处理为null     @Nullable     protected Object fromStoreValue(@Nullable Object storeValue) {         return this.allowNullValues && storeValue == NullValue.INSTANCE ? null : storeValue;     }      //对于要插入缓存的null值,在允许null值的情况下处理为NullValue;否则抛出异常IllegalArgumentException     protected Object toStoreValue(@Nullable Object userValue) {         if (userValue == null) {             if (this.allowNullValues) {                 return NullValue.INSTANCE;             } else {                 throw new IllegalArgumentException("Cache '" + this.getName() + "' is configured to not allow null values but null was provided");             }         } else {             return userValue;         }     }      //get操作依据,查询到缓存值非null,则fromStoreValue转换后包装成SimpleValueWrapper返回     @Nullable     protected ValueWrapper toValueWrapper(@Nullable Object storeValue) {         return storeValue != null ? new SimpleValueWrapper(this.fromStoreValue(storeValue)) : null;     } }

추상 클래스 AbstractValueAdaptingCache Cache 인터페이스를 구현하며, 주로 NULL 값의 처리 로직을 추상화했다.

  • allowNullValues 속성은 NULL 값을 처리하는 캐시를 허용할지 나타낸다

  • fromStoreValue 메서드로 NULL 값을 처리하는 get 작업을, 속성 allowNullValues 이 true인 경우, NullValue를 NULL로 처리

  • toStoreValue 메서드로 NULL 값을 처리하는 put 작업을, 속성 allowNullValues 이 true인 경우 NULL을 NullValue로 처리하고, 그렇지 않으면 예외를 던진다

  • toValueWrapper 메서드가 제공하는 Cache 인터페이스에서 get 메서드의 기본 구현은 캐시에서 값을 읽은 다음, fromStoreValue 변환한 뒤, 마지막으로 다음으로 감쌉니다 SimpleValueWrapper 돌아가기

  • ValueWrapper get(Object key) 및 T get(Object key, @Nullable Classtype) 메서드는 위 메서드를 기반으로 구현됩니다.

  • ValueWrapper get(Object key) 및 @Nullable Classtype) 메서드는 위 메서드를 기반으로 구현됩니다.

  • lookup 추상 메서드는 하위 클래스가 실제 캐시 값을 가져오도록 하는 데 사용된다

ConcurrentMapCache

public class ConcurrentMapCache extends AbstractValueAdaptingCache {     private final String name;     //定义ConcurrentMap缓存     private final ConcurrentMap<Object, Object> store;     //如果要缓存的是值对象的copy,则由此序列化代理类处理     @Nullable     private final SerializationDelegate serialization;      public ConcurrentMapCache(String name) {         this(name, new ConcurrentHashMap(256), true);     }      //默认允许处理null     public ConcurrentMapCache(String name, boolean allowNullValues) {         this(name, new ConcurrentHashMap(256), allowNullValues);     }      //默认serialization = null     public ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues) {         this(name, store, allowNullValues, (SerializationDelegate)null);     }      protected ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues, @Nullable SerializationDelegate serialization) {         super(allowNullValues);         Assert.notNull(name, "Name must not be null");         Assert.notNull(store, "Store must not be null");         this.name = name;         this.store = store;         this.serialization = serialization;     }      //serialization不为空,缓存值对象的copy     public final boolean isStoreByValue() {         return this.serialization != null;     }      public final String getName() {         return this.name;     }      public final ConcurrentMap<Object, Object> getNativeCache() {         return this.store;     }      //实现lookup:store#get     @Nullable     protected Object lookup(Object key) {         return this.store.get(key);     }      //基于ConcurrentMap::computeIfAbsent方法实现;get和put的值由fromStoreValue和toStoreValue处理Null     @Nullable     public <T> T get(Object key, Callable<T> valueLoader) {         return this.fromStoreValue(this.store.computeIfAbsent(key, (k) -> {             try {                 return this.toStoreValue(valueLoader.call());             } catch (Throwable var5) {                 throw new ValueRetrievalException(key, valueLoader, var5);             }         }));     }      public void put(Object key, @Nullable Object value) {         this.store.put(key, this.toStoreValue(value));     }      @Nullable     public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {         Object existing = this.store.putIfAbsent(key, this.toStoreValue(value));         return this.toValueWrapper(existing);     }      public void evict(Object key) {         this.store.remove(key);     }      public boolean evictIfPresent(Object key) {         return this.store.remove(key) != null;     }      public void clear() {         this.store.clear();     }      public boolean invalidate() {         boolean notEmpty = !this.store.isEmpty();         this.store.clear();         return notEmpty;     }      protected Object toStoreValue(@Nullable Object userValue) {         Object storeValue = super.toStoreValue(userValue);         if (this.serialization != null) {             try {                 return this.serialization.serializeToByteArray(storeValue);             } catch (Throwable var4) {                 throw new IllegalArgumentException("Failed to serialize cache value '" + userValue + "'. Does it implement Serializable?", var4);             }         } else {             return storeValue;         }     }      protected Object fromStoreValue(@Nullable Object storeValue) {         if (storeValue != null && this.serialization != null) {             try {                 return super.fromStoreValue(this.serialization.deserializeFromByteArray((byte[])((byte[])storeValue)));             } catch (Throwable var3) {                 throw new IllegalArgumentException("Failed to deserialize cache value '" + storeValue + "'", var3);             }         } else {             return super.fromStoreValue(storeValue);         }     } }

ConcurrentMapCache 추상 클래스를 상속했다 AbstractValueAdaptingCache ,Spring의 기본 캐시 구현이다. 캐시 객체의 복사본을 캐싱하는 기능을 지원하며, SerializationDelegate serialization이 직렬화를 처리한다. 기본값은 null로, 참조 기반 캐싱을 뜻한다. 캐시 관련 연산은 기반 클래스를 바탕으로 한다 AbstractValueAdaptingCache 의 null 값 처리로, 기본적으로 null을 허용한다.

CacheManager

public interface CacheManager {     @Nullable     //获取指定name的Cache,可能延迟创建     Cache getCache(String name);      //获取当前CacheManager下的Cache name集合     Collection<String> getCacheNames(); }

CacheManager 다음을 기반으로 name으로 Cache 그룹을 관리합니다. 물론, CacheManager 에도 구현 클래스가 많이 있으며, 예를 들면 ConcurrentMapCacheManager 、 AbstractCacheManager 및 SimpleCacheManager ,이러한 xxxCacheManager 클래스는 모두 Cache의 관리 규칙을 정하기 위한 것이므로, 여기서는 더 깊이 다루지 않겠다。

이 SpringCache 사용

2.1 사용 방법

Spring Cache가 바로 이런 프레임워크이다. AOP를 활용하여 애너테이션 기반 캐시 기능을 구현하고 적절한 추상화를 제공하므로, 비즈니스 코드는 내부에서 어떤 캐시 프레임워크를 사용하는지 신경 쓸 필요 없이 애너테이션 하나만 간단히 추가하면 캐시 기능을 구현할 수 있다.

의존성 추가

<!--springboot的cache支持--> <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-cache</artifactId> </dependency>  <!--配置拓展还需添加的依赖--> <dependency>   <groupId>com.github.ben-manes.caffeine</groupId>   <artifactId>caffeine</artifactId>   <version>2.9.2</version> </dependency>

Cache 설정

Application 시작 클래스에 @EnableCaching 애너테이션 추가

@SpringBootApplication     @MapperScan("com.xxxx.dao")     @EnableCaching     public class AxxxxApplication {          public static void main(String[] args) {             SpringApplication.run(AxxxxApplication.class, args);         }      }

cache의 캐시 유형을 설정한다, 여기서는 주로 로컬 캐시를 소개한다

//方式一:ConcurrentMapCache (不好设置过期时间) @Configuration public class SpringCachingConfig {        @Bean      public CacheManager cacheManager() {           SimpleCacheManager cacheManager = new SimpleCacheManager();           Set<ConcurrentMapCache> mapCaches=new HashSet<>();           //配置cache类型与目录           mapCaches.add(new ConcurrentMapCache("cacheNames1"));           mapCaches.add(new ConcurrentMapCache("cacheNames2"));           mapCaches.add(new ConcurrentMapCache("cacheNames3"));           mapCaches.add(new ConcurrentMapCache("cacheNames4"));           cacheManager.setCaches(mapCaches);           cacheManager.afterPropertiesSet();           return cacheManager;      }   }     //方式二:CaffeineCache (可设置过期时间) @Configuration public class SpringCachingConfig {        @Bean     public CacheManager cacheManager() {         SimpleCacheManager cacheManager = new SimpleCacheManager();           List<CaffeineCache> caffeineCaches = new ArrayList<>();           //SysConstants.CACHE_NAMES=new ArrayList(         //Arrays.asList("cacheNames1","cacheNames2","cacheNames3","cacheNames4")         //);         for (String  cacheName: SysConstants.CACHE_NAMES) {             caffeineCaches.add(new CaffeineCache(cacheName,                     Caffeine.newBuilder()                             .initialCapacity(50)                             .maximumSize(30000)//最大存储数量                             .expireAfterWrite(2, TimeUnit.HOURS)//过期时间2 小时                             .build()));         }         cacheManager.setCaches(caffeineCaches);         return cacheManager;     }   }     //方式三。。。。还可以配置redis以及其他的方式,这里主要介绍以上两种方式

인터페이스 캐싱 구현

//控制器1 @RestController @RequestMapping("/path1") @Cacheable(cacheNames = "cacheNames1" ,key="#root.methodName+#a0" ) public class Controller1 {   }   //控制器2 @RestController @RequestMapping("/path2") @Cacheable(cacheNames = "cacheNames2" ,key="#root.methodName+#a0" ) public class Controller2 {   }   //控制器3 @RestController @RequestMapping("/path3") @Cacheable(cacheNames = "cacheNames3" ,key="#root.methodName+#a0" ) public class Controller3 {   }   //控制器4 @RestController @RequestMapping("/path4") @Cacheable(cacheNames = "cacheNames4" ,key="#root.methodName+#a0" ) public class Controller4 {   }

2.2 SpringCache 애너테이션 상세 해설

@Cacheable 애너테이션

캐시에 없다면:데이터베이스를 조회하고,캐시에 저장한 뒤,결과를 반환합니다,
캐시에 있다면:결과를 바로 반환한다

매개변수 설명 예시
value 캐시 이름으로, spring 설정 파일에 정의하며, 적어도 하나를 지정해야 한다 예:
@Cacheable(value=”mycache”)
@Cacheable(value={”cache1”,”cache2”}
key 캐시의 key로, 비워 둘 수 있다. 지정할 경우 SpEL 표현식으로 작성해야 하며, 지정하지 않으면 기본적으로 메서드의 모든 매개변수를 조합한다 @Cacheable(value=”testcache”,key=”#userName”)
condition 캐싱 조건으로, 비워 둘 수 있으며 SpEL로 작성하고, true 또는 false를 반환한다. true일 때만 캐싱한다 @Cacheable(value=”testcache”,condition=”#userName.length()>2”)

역할:캐시에 데이터를 쓰는 데 사용할 수 있습니다. 결과를 캐시에 저장해 두면 이후 호출에서는 실제 메서드를 다시 실행할 필요 없이 캐시에 있는 값을 바로 반환할 수 있습니다. 가장 간단한 사용법은 애너테이션 이름=캐시 이름이며, 사용 예시는 다음과 같습니다:

@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache  public Account getAccountByName(String userName) {    // 方法内部实现不考虑缓存逻辑,直接实现业务    System.out.println("real query account."+userName);     return getFromDB(userName);  }

@CacheEvict 어노테이션

@CacheEvict:캐시를 삭제하는 애너테이션으로, 오래된 데이터와 쓸모없는 데이터를 삭제할 때 매우 유용하다. 여기에는 매개변수(allEntries)가 하나 더 있으며, allEntries=true로 설정하면 전체 항목을 일괄 삭제할 수 있다

매개변수 설명 예시
value 캐시 이름으로, spring 설정 파일에 정의하며, 적어도 하나를 지정해야 한다 @CacheEvict(value=”my cache”)
key 캐시의 key로, 비워 둘 수 있다. 지정할 경우 SpEL 표현식으로 작성해야 하며, 지정하지 않으면 기본적으로 메서드의 모든 매개변수를 조합한다 @CacheEvict(value=”testcache”,key=”#userName”)
condition 캐싱 조건으로, 비워 둘 수 있으며 SpEL로 작성하고, true 또는 false를 반환한다. true일 때만 캐싱한다 @CacheEvict(value=”testcache”,condition=”#userName.length()>2”)
allEntries 모든 캐시 내용을 비울지 여부로, 기본값은 false이며, true로 지정하면 메서드 호출 후 즉시 모든 캐시를 비운다 @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 메서드 실행 전에 비울지 여부이며, 기본값은 false입니다. true로 지정하면 메서드가 실행되기도 전에 캐시를 비웁니다. 기본 설정에서는 메서드 실행 중 예외가 발생하면 캐시를 비우지 않습니다 @CachEvict(value=”testcache”,beforeInvocation=true)

 

@CacheEvict(value="accountCache",key="#account.getName()")// 清空accountCache 缓存  public void updateAccount(Account account) {    updateDB(account);  }   @CacheEvict(value="accountCache",allEntries=true)// 清空accountCache 缓存 public void reload() {    reloadAll() }  @Cacheable(value="accountCache",condition="#userName.length() <=4")// 缓存名叫 accountCache  public Account getAccountByName(String userName) {   // 方法内部实现不考虑缓存逻辑,直接实现业务  return getFromDB(userName);  }

@CachePut 애너테이션

@CachePut:메서드 실행에 영향을 주지 않으면서 캐시를 갱신해야 할 때 사용할 수 있는 애너테이션입니다. 즉, 항상 해당 메서드를 실행하고 결과를 캐시에 저장합니다;

매개변수 설명 예시
value 캐시 이름으로, spring 설정 파일에 정의하며, 적어도 하나를 지정해야 한다 @CachePut(value=”my cache”)
key 캐시의 key로, 비워 둘 수 있다. 지정할 경우 SpEL 표현식으로 작성해야 하며, 지정하지 않으면 기본적으로 메서드의 모든 매개변수를 조합한다 @CachePut(value=”testcache”,key=”#userName”)
condition 캐싱 조건으로, 비워 둘 수 있으며 SpEL로 작성하고, true 또는 false를 반환한다. true일 때만 캐싱한다 @CachePut(value=”testcache”,condition=”#userName.length()>2”)

@CachePut 애너테이션은 메서드가 실행되도록 보장하는 동시에 메서드의 반환값도 캐시에 저장하여, 캐시와 데이터베이스를 함께 갱신한다.

@CachePut(value="accountCache",key="#account.getName()")// 更新accountCache 缓存 public Account updateAccount(Account account) {    return updateDB(account);  }

@Caching 애너테이션

캐시를 사용할 때 갱신과 삭제를 동시에 수행할 수도 있어, 여러 어노테이션을 함께 사용하는 경우가 생깁니다. 그리고 @Caching을 사용하면 구현할 수 있습니다

//添加user缓存的同时,移除userPage的缓存 @Caching(     put =@CachePut(value = "user",key ="#userVo.id"), evict = @CacheEvict(value = "userPage",allEntries = true) )
@Caching(put = { @CachePut(value = "user", key = "#user.id"), @CachePut(value = "user", key = "#user.username"), @CachePut(value = "user", key = "#user.email") }) public User save(User user) {

@CacheConfig

모든 @Cacheable()에는 value=“xxx”라는 속성이 있습니다. 메서드가 많아지면 작성하기도 꽤 번거롭겠죠. 한 번에 선언할 수 있다면 편리할 것입니다. 그래서 @CacheConfig라는 설정이 생겼습니다. @CacheConfig는 캐시 이름을 공유할 수 있게 하는 클래스 수준의 어노테이션입니다. 메서드에 다른 이름을 지정하면, 여전히 메서드에 지정한 이름이 우선합니다.

@CacheConfig("books") public class BookRepositoryImpl implements BookRepository {    @Cacheable   public Book findBook(ISBN isbn) {...} }

조건부 캐싱
아래에 자주 사용하는 조건부 캐싱 몇 가지를 제시합니다

//@Cacheable将在执行方法之前( #result还拿不到返回值)判断condition,如果返回true,则查缓存;  @Cacheable(value = "user", key = "#id", condition = "#id lt 10") public User conditionFindById(final Long id)   //@CachePut将在执行完方法后(#result就能拿到返回值了)判断condition,如果返回true,则放入缓存;  @CachePut(value = "user", key = "#id", condition = "#result.username ne 'zhang'")  public User conditionSave(final User user)    //@CachePut将在执行完方法后(#result就能拿到返回值了)判断unless,如果返回false,则放入缓存;(即跟condition相反) @CachePut(value = "user", key = "#user.id", unless = "#result.username eq 'zhang'") public User conditionSave2(final User user)    //@CacheEvict, beforeInvocation=false表示在方法执行之后调用(#result能拿到返回值了);且判断condition,如果返回true,则移除缓存; @CacheEvict(value = "user", key = "#user.id", beforeInvocation = false, condition = "#result.username ne 'zhang'")  public User conditionDelete(final User user)

사용자 정의 캐시 어노테이션

예를 들어 앞서 나온 @Caching 조합은 메서드에 붙은 애너테이션 때문에 코드 전체가 다소 어수선해 보이게 한다. 이때 사용자 정의 애너테이션으로 이 애너테이션들을 하나로 묶을 수 있다. 예:

@Caching(put = { @CachePut(value = "user", key = "#user.id"), @CachePut(value = "user", key = "#user.username"), @CachePut(value = "user", key = "#user.email") }) @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface UserSaveCache { }

이렇게 하면 메서드에 아래 코드만 사용하면 되므로, 전체 코드가 한결 깔끔해진다.

@UserSaveCache public User save(User user)

확장
예를 들어 findByUsername을 사용할 때,username–>user만 넣어서는 안 되며,id—>user와 email—>user도 함께 넣어야 한다;이렇게 하면 다음에 id로 조회할 경우 캐시에서 바로 찾을 수 있다

@Caching(   cacheable = {     @Cacheable(value = "user", key = "#username")   },   put = {     @CachePut(value = "user", key = "#result.id", condition = "#result != null"),     @CachePut(value = "user", key = "#result.email", condition = "#result != null")   } ) public User findByUsername(final String username) {   System.out.println("cache miss, invoke find by username, username:" + username);   for (User user : users) {     if (user.getUsername().equals(username)) {       return user;     }   }   return null; }

사실:id—>user;username—->user;email—>user;의 경우, 더 나은 방법은:id—>user;username—>id;email—>id;일 수 있다. user를 하나만 저장하도록 하는 것이다;예:

@CachePut(value="cacheName", key="#user.username", cacheValue="#user.username")  public void save(User user)     @Cacheable(value="cacheName", key="#user.username", cacheValue="#caches[0].get(#caches[0].get(#username).get())")  public User findByUsername(String username)

SpEL 컨텍스트 데이터
Spring Cache는 사용할 수 있는 SpEL 컨텍스트 데이터를 제공합니다. 아래 표는 Spring 공식 문서에서 직접 발췌한 것입니다:

이름 위치 설명 예시
methodName root 객체 현재 호출된 메서드 이름 root.methodName
method root 객체 현재 호출되는 메서드 root.method.name
target root 객체 현재 호출되는 대상 객체 root.target
targetClass root 객체 현재 호출되는 대상 객체의 클래스 root.targetClass
args root 객체 현재 호출된 메서드의 매개변수 목록 root.args[0]
caches root 객체 현재 메서드 호출에 사용되는 캐시 목록(예를 들어 @Cacheable(value={“cache1”, “cache2”})에는 캐시가 두 개 있음) root.caches[0].name
인수 이름 실행 컨텍스트 현재 호출되는 메서드의 매개변수로, 예를 들어 findById(Long id)의 경우 #id를 통해 매개변수를 가져올 수 있다 user.id
result 실행 컨텍스트 메서드 실행 후의 반환값(메서드 실행 후에 판단하는 경우에만 유효,예:‘unless','cache evict'의 beforeInvocation=false) result
@CacheEvict(value = "user", key = "#user.id", condition = "#root.target.canCache() and #root.caches[0].get(#user.id).get().username ne #user.username", beforeInvocation = true)  public void conditionUpdate(User user)

소결

캐시 선언을 위해 spring의 캐시 기능은 다음과 같은 java 애너테이션을 제공한다:
· @Cacheable
o 기능:캐시 쓰기를 실행한다. 캐시에 없으면 데이터베이스를 조회해 캐시에 저장하고 결과를 반환하며, 캐시에 있으면 바로 결과를 반환한다
o 활용:데이터베이스 조회메서드
· @CacheEvict
o 기능:캐시 삭제 실행
o 활용:데이터베이스 삭제 또는 수정메서드
· @CachePut
o 기능:캐시에 쓰기(메서드 실행에는 영향을 주지 않는다).
o 활용:데이터베이스에 추가메서드
· @캐싱
o 기능:메서드에 적용할 여러 캐시 작업을 다시 조합
o 활용:위 애너테이션들을 조합하여 사용


댓글 공간

보내는 중

원하는 테마를 선택하세요

선택한 테마가 사이트 전체에 적용됩니다.