Skip to main content

Search CooLWJ

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

Language

Introduction to Using Spring Cache

I. A caching solution based on SpringCache

1.1 Introduction to SpringCache

springcache was released in spring3.1. It encapsulates and abstracts cache usage, allowing cached results to be retrieved by applying annotations to methods. Precisely because it uses annotations, it addresses the coupling between business code and caching code, enabling existing code to support caching immediately without intrusive changes to business code and allowing developers to use caching transparently.

1.2 Overview of SpringCache

There are many caching frameworks, each with its own strengths, such as Redis, Memcached, Guava, Caffeine, and others.
If our program needs to use caching, it must be coupled to these frameworks. Savvy architects are already using interfaces to reduce coupling, drawing on object-oriented abstraction and polymorphism to separate business code from specific frameworks.
But we still need to explicitly call cache-related interfaces and methods in our code, inserting data into the cache at the appropriate time and reading data from it at the appropriate time. Think about「AOP」’s use cases—isn’t this naturally something AOP should handle?
Since Spring 3.1, annotation-based Cache support similar to the [@Transactional](/Transactional ) annotation for transactions has been available, along with a Cache abstraction. Before that, it was generally implemented through AOP.
Benefits of using Spring Cache:

  • Provides a basic Cache abstraction, making it easy to switch between different underlying Cache implementations;

  • With Cache annotations, caching logic can be applied transparently to our business code, much like transactions, and requires less code to implement;

  • Automatically rolls back the cache when a transaction is rolled back;

  • Supports relatively complex caching logic;

Spring Cache technology logo

1.3 Core Design of Spring Cache

Starting with Spring 3.1, Spring Cache, the Spring caching abstraction, was introduced. By defining org.springframework.cache.Cache and org.springframework.cache.CacheManager interface to unify different caching technologies, with support for JCache annotations to simplify development.
The Cache interface defines the specification for cache components, including a collection of cache operations. Spring provides various xxxCache  implementation: RedisCache , EhCacheCache , ConcurrentMapCache and so on.
Let’s examine some source code to understand the Cache interface and CacheManager interface.

Cache interface

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();     } }

As we can see, the Cache interface abstracts cache operations such as get, put, and evict.

The AbstractValueAdaptingCache abstract class

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;     } }

Abstract class AbstractValueAdaptingCache Implements the Cache interface, primarily abstracting the logic for handling NULL values.

  • allowNullValues property indicates whether caching of NULL values is allowed

  • fromStoreValue using the method to handle NULL values get operation, in the property allowNullValues When true, NullValue is treated as NULL

  • toStoreValue method to handle NULL values put operation, in the property allowNullValues When true, NULL is handled as NullValue; otherwise, an exception is thrown

  • toValueWrapper provided by the method Cache in the interface get method's default implementation, reading the value from the cache, then using fromStoreValue conversion, finally wrapped as SimpleValueWrapper Back

  • ValueWrapper get(Object key) and T get(Object key, @Nullable Classtype) method is implemented based on the method above

  • ValueWrapper get(Object key) and @Nullable Classtype) method is implemented based on the method above

  • lookup The abstract method lets subclasses obtain the actual cached value

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 extends the abstract class AbstractValueAdaptingCache , is Spring's default cache implementation. It supports caching copies of cached objects, with serialization handled by SerializationDelegate serialization, which defaults to null, meaning reference-based caching. Cache-related operations are based on the base class AbstractValueAdaptingCache 's null value handling; null is allowed by default.

CacheManager

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

CacheManager Manages a group of Cache instances based on name. Of course, CacheManager also has many implementation classes, such as ConcurrentMapCacheManager 、 AbstractCacheManager and SimpleCacheManager , these xxxCacheManager classes are all intended to define Cache management rules, so we will not explore them further here.

II Using SpringCache

2.1 How to Use

Spring Cache is one such framework. It uses AOP to implement annotation-based caching and provides a sensible abstraction, so business code does not need to concern itself with the underlying caching framework. Simply adding an annotation enables caching.

Add dependencies

<!--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>

Configure Cache

Add the @EnableCaching annotation to the Application startup class

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

Configure the cache type; the main focus here is local caching

//方式一: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以及其他的方式,这里主要介绍以上两种方式

Implement API caching

//控制器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 annotations explained

@Cacheable annotation

If it is not in the cache: query the database, store it in the cache, return the result,
If it is in the cache: return the result directly

Parameters Explanation example
value The cache names, defined in the spring configuration file; at least one must be specified For example:
@Cacheable(value=”mycache”)
@Cacheable(value={”cache1”,”cache2”}
key The cache key can be empty. If specified, it must be written as a SpEL expression; if omitted, it defaults to a combination of all the method's parameters @Cacheable(value=”testcache”,key=”#userName”)
condition The caching condition, which can be empty, is written in SpEL and returns true or false. Caching occurs only when it is true @Cacheable(value=”testcache”,condition=”#userName.length()>2”)

Purpose: It can be used to write to the cache, storing the result so that subsequent calls can return the cached value directly without executing the actual method again. The simplest usage is annotation name=cache name, as shown in the following example:

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

The @CacheEvict annotation

@CacheEvict: An annotation for deleting cached entries, which is very useful for removing old and useless data. It also has an additional parameter (allEntries); setting allEntries=true allows all entries to be deleted in bulk

Parameters Explanation example
value The cache names, defined in the spring configuration file; at least one must be specified @CacheEvict(value=”my cache”)
key The cache key can be empty. If specified, it must be written as a SpEL expression; if omitted, it defaults to a combination of all the method's parameters @CacheEvict(value=”testcache”,key=”#userName”)
condition The caching condition, which can be empty, is written in SpEL and returns true or false. Caching occurs only when it is true @CacheEvict(value=”testcache”,condition=”#userName.length()>2”)
allEntries Whether to clear all cached content; defaults to false. If set to true, all cached content will be cleared immediately after the method call @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation Whether to clear the cache before the method executes; the default is false. If set to true, the cache is cleared before the method executes. By default, if the method throws an exception, the cache is not cleared @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);  }

The @CachePut Annotation

@CachePut: Use this annotation when you need to update the cache without interfering with the method's execution. In other words, the method always executes, and its result is placed in the cache;

Parameters Explanation example
value The cache names, defined in the spring configuration file; at least one must be specified @CachePut(value=”my cache”)
key The cache key can be empty. If specified, it must be written as a SpEL expression; if omitted, it defaults to a combination of all the method's parameters @CachePut(value=”testcache”,key=”#userName”)
condition The caching condition, which can be empty, is written in SpEL and returns true or false. Caching occurs only when it is true @CachePut(value=”testcache”,condition=”#userName.length()>2”)

The @CachePut annotation ensures that the method executes and that its return value is also stored in the cache, updating the cache and database in sync.

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

@Caching annotation

When using a cache, updates and deletions may occur together, requiring multiple annotations to be used at the same time. And @Caching can achieve

//添加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

Every @Cacheable() has a value=“xxx” attribute. Clearly, writing this repeatedly becomes tedious when there are many methods. It would be easier to declare it once, which is why @CacheConfig exists. @CacheConfig is a class-level annotation that allows cache names to be shared. If you specify a different name on a method, the name specified on that method still takes precedence.

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

Conditional Caching
Below are some commonly used conditional caching options

//@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)

Custom Cache Annotations

For example, the earlier @Caching combination can make the annotations on a method look rather cluttered. In this case, you can use a custom annotation to combine them into a single annotation, as follows:

@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 { }

We can then use the following code on the method, keeping the overall code fairly clean.

@UserSaveCache public User save(User user)

Extensions
For example, when calling findByUsername, you should cache username–>user together with id—>user and email—>user; this way, the next lookup by id will directly hit the cache

@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; }

In fact, for: id—>user;username—->user;email—>user;a better approach might be: id—>user;username—>id;email—>id;ensuring that only one copy of user is stored;for example:

@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 context data
Spring Cache provides some SpEL context data for us to use. The following table is taken directly from the official Spring documentation:

Name Location Description Example
methodName root object The name of the method currently being called root.methodName
method root object The method currently being called root.method.name
target root object The target object currently being invoked root.target
targetClass root object The class of the target object currently being invoked root.targetClass
args root object The parameter list of the method currently being called root.args[0]
caches root object The list of caches used by the current method invocation (for example, @Cacheable(value={“cache1”, “cache2”})), in which case there are two caches root.caches[0].name
argument name Execution Context The parameters of the method currently being invoked; for example, with findById(Long id), we can access the parameter through #id user.id
result Execution Context The return value after method execution (available only for conditions evaluated after execution, such as ‘unless' and 'cache evict' with 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)

Recap

For cache declarations, Spring's caching support provides a set of Java annotations:
· @Cacheable
o Function: Triggers a cache write. If the data is absent from the cache, queries the database, caches the data, and returns the result; if the data is present in the cache, returns the result directly
o Applications:Query the database Method
· @CacheEvict
o Function: Trigger cache eviction
o Applications:Delete or modify the database Method
· @CachePut
o Function: Writes to the cache (does not affect the execution of the method).
o Applications:Add to the database Method
· @Caching
o Function: Combine multiple caching operations to be applied to a method
o Application: Using the annotations above in combination


Comments

Sending

Choose your theme

Your theme applies across the site.