Cache of SpringBoot

SpringBoot and caching

​ With the accumulation of time, the number of users of the application continues to increase, and the size of the data is also increasing. Often, the database query operation will become a bottleneck that affects the user experience. At this time, using cache is often one of the best ways to solve this problem. Spring 3 began to provide powerful annotation-based caching support, which can add caching functions to original Spring applications and improve data access performance through annotation configuration with low intrusion. The support for caching in Spring Boot provides a series of automatic configurations, so that we can use caching very conveniently.

First understand the concepts of JSR107, Spring cache abstraction and so on.

1. The role of Cache cache

1. JSR107

Java Caching defines five core interfaces, namely CachingProvider, CacheManager, Cache, Entry and Expiry.

  1. CachingProvider defines the creation, configuration, acquisition, management and control of multiple CacheManagers. An application can access multiple CachingProviders at runtime.

  2. CacheManager defines the creation, configuration, acquisition, management and control of multiple uniquely named Cache, which exist in the context of CacheManager. A CacheManager is owned by only one CachingProvider.

  3. Cache is a data structure similar to Map and temporarily stores values ​​indexed by Key. A Cache is owned by only one CacheManager.

  4. Entry is a key-value pair stored in Cache.

  5. Expiry Every entry stored in the Cache has a defined expiration date. Once this time expires, the entry is in the expired state. Once expired, entries cannot be accessed, updated and deleted. The cache validity period can be set through ExpiryPolicy.
    insert image description here

2. Spring cache abstraction

Spring has defined org.springframework.cache.Cache and org.springframework.cache.CacheManager interfaces since 3.1 to unify different caching technologies; and supports the use of JCache (JSR-107) annotations to simplify our development.

  • The Cache interface is defined by the component specification of the cache, and includes a collection of various operations of the cache.

  • Under the Cache interface, Spring provides various xxxCache implementations; such as RedisCache, EhCacheCache, ConcurrentMapCache.

  • Every time a method that requires caching is called, Spring will check whether the specified target method of the specified parameter has been called; if so, it will directly obtain the result of the method call from the cache, if not, it will call the method and cache the result and return it to the user. The next call is fetched directly from the cache.

  • When using Spring cache abstraction, we need to pay attention to the following two points:

    1. Determine the methods that need to be cached and their caching strategy

2. Read the previously cached data from the cache

2. Several important concepts & cache annotations

Concept/Notes effect
Cache Cache interface, defines cache operations. Implementations include: RedisCache, EhCacheCache, ConcurrentMapCache, etc.
CacheManager Cache manager, manages various cache (Cache) components
@Cacheable It can be configured for methods and classes, mainly for method configuration, and the results can be cached according to the request parameters of the method. If the cache
exists, the cache is used; if it does not exist, the method is executed and the result is stuffed into the cache
@CacheEvict Empty the cache
@CachePut The method is guaranteed to be called, and the result is expected to be cached. The difference from @Cacheable is whether the method is called every time, which is often used for updating
@Caching The combination of @Cacheable, @CachePut and @CacheEvict defines complex caching rules. As long as there is @CachePut in this combination, the annotated method will be called
@CacheConfig Marked on the class to extract the public configuration of the cache-related annotations. The common configurations that can be extracted include the cache name, primary key generator, etc.
@EnableCaching Enable annotation-based caching
keyGenerator Key generation strategy when caching data
serialize Value serialization strategy when caching data

Note:
   ① @Cacheable is marked on the method, indicating that the result of the method needs to be cached. The cache key is determined by the keyGenerator strategy, and the format of the cached value is determined by the serialize serialization strategy (serialization or json format); after marking this annotation, when the method is called again within the cache time limit, the method itself will not be called but the result will be obtained directly from the cache. ②@CachePut is also marked on the method, which is similar to @Cacheable and will also cache the return value of the method. The CachePut method will be called every time, and the result will be cached every time, which is suitable for object
   update

@Cacheable

The main parameters

attribute name describe example
cacheNames/value cacheNames and value are aliases for each other. Its function is to specify the name of the cache. The cache uses CacheManager to manage multiple cache components Cache, and these Cache components are distinguished according to this name. The real CRUD operations on the cache are defined in the Cache. Each cache component Cache has its own unique name, which is specified by the cacheNames or value attribute, which is equivalent to grouping the key-value pairs of the cache. The name of the cache is an array, which means that a cache key-value pair can be divided into multiple groups. @Cacheable(value=“testCache”) 或者 @Cacheable(cacheNames={“cache1”,“cache2”})
key The value of the key when caching data, the default is to use the value of the method parameter, you can use the SpEL expression to calculate the value of the key @Cacheable(value=“testCache”,key=“#userName”)
keyGenerator The generation strategy of the cache, and the choice of the key, both generate keys, and the keyGenerator can be customized
cacheManager Specify the cache manager (such as ConcurrentHashMap, Redis, etc.)
cacheResolver Same function as cacheManager, choose one from cacheManager
condition Specify the conditions of the cache (cache only when the conditions are met), and use SpEL expressions (such as #id>0, which means that the cache will only be cached when the input parameter id is greater than 0) @Cacheable(value=“testCache”,condition=“#userName.length()>2”
unless Negative caching, that is, when the condition specified by unless is met, the result of the method will not be cached. When using unless, you can judge after the called method obtains the result (such as #result==null, indicating that if the result is null, it will not be cached) @Cacheable(value=“testCache”,unless=“#result == null”)
sync Whether to use asynchronous mode for caching; when using asynchronous mode for caching (sync=true): the unless condition will not be supported

Note:
  ①If both the condition and the unless condition are met, the cache will not be performed.
  ②When using asynchronous mode for caching (sync=true): the unless condition will not be supported

The available SpEL expressions are listed in the table below:

name Location describe example
methodName root object The name of the currently called method #root.methodName
method root object the currently called method #root.method.name
target root object the currently called target object #root.target
targetClass root object The currently called target object class root.targetClass
args root object The parameter list of the currently called method #root.args[0]
caches root object The cache list used by the current method call (such as @Cacheable(value={"cache1", "cache2"})), there are two caches #root.caches[0].name
Argument Name evaluation context The parameters of the currently called method can directly #parameter name, such as findArtisan(Artisan artisan), you can get the parameters through #artsian.id; you can also use the form of #p0 or #a0, 0 represents the index of the parameter #iban、#a0、#p0
result evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效,如"unless","cache put"的表达式,"cache evict"的表达式beforeInvocation=false) #result

详解

​ 这个注解用于修饰方法或者类,当我们访问它修饰的方法时,优先从缓存中获取,若缓存中存在,则直接获取缓存的值;缓存不存在时,则执行方法,并将结果写入缓存。

这个注解,有两个比较核心的设置

 /**
  * 与 cacheNames 效果等价
  */
 @AliasFor("cacheNames")
 String[] value() default {
    
    };

 /**
  * 与 value 效果等价
  */
 @AliasFor("value")
 String[] cacheNames() default {
    
    };

 /**
  * 缓存key
  */
 String key() default "";

cacheNames 可以理解为缓存 key 的前缀,可以为组件缓存的 key 变量;当 key 不设置时,使用方法参数来初始化,注意 key 为 SpEL 表达式,因此如果要写字符串时,用单引号括起来。

/**
 * 首先从缓存中查,查到之后,直接返回缓存数据;否则执行方法,并将结果缓存
 *
 * redisKey: cacheNames + key 组合而成 --> 支持SpEL
 * redisValue: 返回结果
 */
@Cacheable(cacheNames = "say", key = "'p_'+ #name")
public String sayHello(String name) {
    
    
    return "hello+" + name + "-->" + UUID.randomUUID().toString();
}

如我们传参为 somnus, 那么缓存 key 为 say::somnus

condition参数,这个表示当它设置的条件达成时,才写入缓存。下面这个 case 中,age 为偶数的时候,才走缓存;否则不走缓存。

/**
 * 满足condition条件的才写入缓存
 */
@Cacheable(cacheNames = "condition", key = "#age", condition = "#age % 2 == 0")
public String setByCondition(int age) {
    
    
    return "condition:" + age + "-->" + UUID.randomUUID().toString();
}

接下来是unless参数,这个表示它设置的条件不满足时才写入缓存。下面这个 case 中,age 为偶数的时候,不走缓存;否则走缓存。

/**
 * unless, 不满足条件才写入缓存
 */
@Cacheable(cacheNames = "unless", key = "#age", unless = "#age % 2 == 0")
public String setUnless(int age) {
    
    
    return "unless:" + age + "-->" + UUID.randomUUID().toString();
}

service层代码

第一次查询数据库打印service类方法日志,并把数据保存到Cahce中

第二次传入相同参数不再执行service类方法,不会打印日志,查询的数据直接从缓存中获取

@Service
public class PersonService {
    
    
  
    @Autowired
    PersonDao personDao;
 
     //@Cacheable(cacheNames= "person")
     //@Cacheable(cacheNames= "person",key="#id",condition="#id>3")
     @Cacheable(cacheNames= "person",key="#id")
     public Person queryPersonById(Integer id){
    
    
        System.out.println("查询"+id+"号员工信息");
        Person person=new Person();
        person.setId(id);
        return personDao.query(person);
    }
}

@CachePut

不管缓存有没有,都将方法的返回结果写入缓存;适用于缓存更新

通俗讲就是:既调用方法,又更新缓存数据 ,即数据库中的数据和缓存都更新!

主要参数

属性名 描述 示例
cacheNames/value cacheNames和value互为别名。其作用是指定缓存的名字,缓存使用CacheManager管理多个缓存组件Cache,这些Cache组件就是根据这个名字进行区分的。对缓存的真正CRUD操作在Cache中定义,每个缓存组件Cache都有自己唯一的名字,通过cacheNames或者value属性指定,相当于是将缓存的键值对进行分组,缓存的名字是一个数组,也就是说可以将一个缓存键值对分到多个组里面。 @CachePut(value=“testCache”) 或者 @CachePut(cacheNames={“cache1”,“cache2”})
key 缓存数据时的key的值,默认是使用方法参数的值,可以使用SpEL表达式计算key的值 @CachePut(value=“testCache”,key=“#userName”)
keyGenerator 缓存的生成策略,和key二选一,都是生成键的,keyGenerator可自定义
cacheManager 指定缓存管理器(如ConcurrentHashMap、Redis等)
cacheResolver 和cacheManager功能一样,和cacheManager二选一
condition 指定缓存的条件(满足什么条件时才缓存),可用SpEL表达式(如#id>0,表示当入参id大于0时才缓存) @CachEvict(value=“testCache”,condition=“#userName.length()>2”
unless 否定缓存,即满足unless指定的条件时,方法的结果不进行缓存,使用unless时可以在调用的方法获取到结果之后再进行判断(如#result==null,表示如果结果为null时不缓存) @Cacheable(value=“testCache”,unless=“#result == null”)
sync 是否使用异步模式进行缓存;使用异步模式进行缓存时(sync=true):unless条件将不被支持

详解

@Service
public class PersonService {
    
    
    @Autowired
    PersonDao personDao;
   /**
     *运行时机:
     * 1.先调用目标方法
     * 2.将目标方法返回的结果缓存起来
     *
     * 测试步骤:
     * 1.查询1号的个人信息
     * 2.以后查询还是之前的结果
     * 3.更新1号的个人信息
     * 4.查询一号员工返回的结果是什么?
     *     应该是更新后的员工
     *     但只更新了数据库,但没有更新缓存是什么原因?
     * 5.如何解决缓存和数据库同步更新?
     * 这样写:@CachePut(cacheNames = "person",key = "#person.id")
     *       @CachePut(cacheNames = "person",key = "#result.id")
     */
    @CachePut(cacheNames = "person",key = "#result.id")
    public Person updatePerson(Person person){
    
    
        System.out.println("修改"+person.getId()+"号员工信息");
        personDao.update(person);
        return person;
    }
}

@CacheEvict

这个就是我们理解的删除缓存,可以清除缓存中的指定数据或清除缓存中所有数据。

主要参数

属性名 描述 示例
cacheNames/value cacheNames和value互为别名。其作用是指定缓存的名字,缓存使用CacheManager管理多个缓存组件Cache,这些Cache组件就是根据这个名字进行区分的。对缓存的真正CRUD操作在Cache中定义,每个缓存组件Cache都有自己唯一的名字,通过cacheNames或者value属性指定,相当于是将缓存的键值对进行分组,缓存的名字是一个数组,也就是说可以将一个缓存键值对分到多个组里面。 @CachEvict(value=“testCache”) 或者 @CachEvict(cacheNames={“cache1”,“cache2”})
key 缓存数据时的key的值,默认是使用方法参数的值,可以使用SpEL表达式计算key的值 @CachEvict(value=“testCache”,key=“#userName”)
keyGenerator 缓存的生成策略,和key二选一,都是生成键的,keyGenerator可自定义
cacheManager 指定缓存管理器(如ConcurrentHashMap、Redis等)
cacheResolver 和cacheManager功能一样,和cacheManager二选一
condition 指定缓存的条件(满足什么条件时才缓存),可用SpEL表达式(如#id>0,表示当入参id大于0时才缓存) @CachEvict(value=“testCache”,condition=“#userName.length()>2”)
allEntries 是否清空所有缓存内容,缺省为 false;如果指定为 true,则方法调用后将立即清空所有缓存 @CachEvict(value=“testCache”,allEntries=true)
beforeInvocation 是否在方法执行前就清空,缺省为 false;如果指定 为 true,则在方法还没有执行的时候就清空缓存, 缺省情况下,如果方法执行抛出异常,则不会清空缓存 @CachEvict(value=“testCache”,beforeInvocation=true)

详解

清除缓存中的单个数据

/**
 * 失效缓存
 */
@CacheEvict(cacheNames = "say", key = "'p_'+ #name")
public String evict(String name) {
    
    
    return "evict+" + name + "-->" + UUID.randomUUID().toString();
}

清除缓存中的所有数据

@Service
public class PersonService {
    
    
    @Autowired
    PersonDao personDao;
 
    /**
     * @CacheEvict:清除缓存
     *    1.key:指定要清除缓存中的某条数据
     *    2.allEntries=true:删除缓存中的所有数据
     *    3.beforeInvocation=false:默认是在方法之后执行清除缓存
     *      beforeInvocation=true:现在是在方法执行之前执行清除缓存
     */
    //@CacheEvict(cacheNames = "person",key = "#id")
    @CacheEvict(cacheNames = "person",allEntries=true)
    public void deletePerson(Integer id){
    
    
        System.out.println("删除"+id+"号个人信息");
        //删除数据库数据的同时删除缓存数据
        //personDao.delete(id);
 
        /**
         * beforeInvocation=true
         * 使用在方法之前执行的好处:
         * 1.如果方法出现异常,缓存依旧会被删除
         */
        //int a=1/0;
    }
}

也可以使用方法来生成key,格式为T(类的全类名).方法名(参数列表)

@Service
public class PersonService {
    
    

    @Autowired
    private PersonDao personDao;

    //@CacheEvict(cacheNames = "person",key = "#id")
    @CacheEvict(key = "T(org.zpli.service.PersonCacheUtils).generateIdKey(#result.id)")
    public Person deletePerson(Person person) {
    
    
        System.out.println("删除" + person.getId() + "号个人信息");
        //删除数据库数据的同时删除缓存数据
        Person deletePerson = personDao.delete(person);
        return deletePerson;
    }
}
package org.zpli.service;


/**
 * created at 2023/3/9 16:52
 *
 * @author somnuszpli
 */
public class PersonCacheUtils {
    
    

    public static String generateIdKey(String id) {
    
    
        return "p_" + id;
    }

}

@Caching

在实际的工作中,经常会遇到一个数据变动,更新多个缓存的场景,对于这个场景,可以通过@Caching来实现

/**
 * caching实现组合,添加缓存,并失效其他的缓存
 */
@Caching(cacheable = @Cacheable(cacheNames = "caching", key = "#age"), evict = @CacheEvict(cacheNames = "t4", key = "#age"))
public String caching(int age) {
    
    
    return "caching: " + age + "-->" + UUID.randomUUID().toString();
}

上面这个就是组合操作

  • caching::age缓存取数据,不存在时执行方法并写入缓存;
  • 失效缓存 t4::age
@Service
public class PersonService {
    
    
    @Autowired
    PersonDao personDao;
   /**
     *   @Caching是 @Cacheable、@CachePut、@CacheEvict注解的组合
     *   以下注解的含义:
     *   1.当使用指定名字查询数据库后,数据保存到缓存
     *   2.现在使用id、age就会直接查询缓存,而不是查询数据库
     */
    @Caching(
            cacheable = {
    
    @Cacheable(value = "person",key="#name")},
            put={
    
     @CachePut(value = "person",key = "#result.id"),
                  @CachePut(value = "person",key = "#result.age")
                }
    )
    public Person queryPersonByName(String name){
    
    
        System.out.println("查询的姓名:"+name);
        return personDao.queryByName(name);
    }
}

三、使用缓存

第一步: 导入spring-boot-starter-cache模块

第二步: @EnableCaching开启缓存

@SpringBootApplication
@EnableCaching
public class SpringbootCacheApplication {
    
    

   public static void main(String[] args) {
    
    
      SpringApplication.run(SpringbootCacheApplication.class, args);
   }
}

第三步: 使用缓存注解

四、缓存工作原理

1、自动配置类:CacheAutoConfiguration,通过CacheAutoConfiguration导入的CacheConfigurationImportSelector会向数组中添加一些缓存的配置类全类名
2、缓存的配置类

​ org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
  org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
  org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
  org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
  org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
  org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
  org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
  org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
  org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
  org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration(默认使用)
  org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
3、默认生效的配置类:SimpleCacheConfiguration
4、SimpleCacheConfiguration给容器中注册了一个CacheManager:ConcurrentMapCacheManager

@Configuration
@ConditionalOnMissingBean({
    
    CacheManager.class})
@Conditional({
    
    CacheCondition.class})
class SimpleCacheConfiguration {
    
    
    private final CacheProperties cacheProperties;
    private final CacheManagerCustomizers customizerInvoker;
    
	SimpleCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) {
    
    
    this.cacheProperties = cacheProperties;
    this.customizerInvoker = customizerInvoker;
	}

	@Bean
	public ConcurrentMapCacheManager cacheManager() {
    
    
    ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
    List<String> cacheNames = this.cacheProperties.getCacheNames();
    if (!cacheNames.isEmpty()) {
    
    
        cacheManager.setCacheNames(cacheNames);
    	}

    return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
	}
}

 Core:   1️⃣ Use CacheManager (ConcurrentMapCacheManager) to get Cache (ConcurrentMapCache) component by name













  2️⃣key is generated using keyGenerator, and SimpleKeyGenerator is used by default

Guess you like

Origin blog.csdn.net/ToBeMaybe_/article/details/126707473