SpringBoot从入门到经通过—Spring —Cache

1、JSR-107规范

1.1 JSP-107是什么?

        要回答这个问题,首先要知道JSR是什么,JSR是Java Specification Requests 的缩写 ,Java规范请求,故名思议就是Java规范,大家一同遵守这个规范的话,会让大家‘沟通’起来更加轻松。规范是很重要的 ,举个例子大家都知道红灯停,路灯行吧,如果每个城市的信号灯代表不一样,那就麻烦了,B城市红灯行,绿灯停,C城市甚至出现紫灯行,闪灯行,想想都知道,如果我们保证不出问题,必须知道每个城市的信号等代表的意义。我们一直使用的JDBC就一个访问数据库的一个规范的例子。
而 JSR-107呢就是关于如何使用缓存的规范。

1.2 JSR-107核心API

Java Caching定义了5个核心接口,分别是CachingProvider,CacheManager,Cache,EntryExpiry

  • CachingProvider
    用于定义创建、配置、获取、管理和控制Cachemanager。

  • CacheManager
    用于定义了建立,配置,得到,管理和控制有着唯一名字的Cache ,一个CacheManager被包含在单一的CachingProvider。

  • Cache
    Cache是一个Map类型的数据结构,用来存储基于键的数据,很多方面都像java.util.Map数据类型。一个Cache 存在在单一的CacheManager。

  • Entry
    Entry是一个存在于Cache的key-value键值对

  • Expiry
    每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy来设置。

这些接口之间的关系可以用下图表示:

2、Spring缓存抽象

Spring 3.1从开始定义了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口来统一不同的缓存技术,并使用JCache(JSR-107)注解简化我们的开发。

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
  • Cache接口下Spring提供了各种xxxCache的实现,如RedisCache、EncheCache、ConcurrentMapCache……
  • 每次调用需要缓存功能的方法是,Spring会检查指定的参数的是定目标方法时候已经被调用过,如果有就直接从缓存中获取方法调用后对结果,如果没有就调用方法去数据库查询并缓存结果后返回给用户,下次回直接从缓存中获取。

重要的缓存注解


注解功能
Cache缓存接口,定义缓存操作。
CacheManager缓存管理器。管理和中缓存组件
@Cacheable主要用于方法,能够根据方法的请求参数对其进行缓存
@CachePut方法被调用,并且在调用后结果被缓存,主要用于更新操作
@CacheEvict清除缓存
@Caching配置复杂对缓存策略
@CacheConfig同一配置本类的缓存注解额属性
@EnableCaching开启基于注解的缓存
serialize缓存数据的value序列haul策略

@Cacheable/@CachePut/@CacheEvict 主要的参数

参数解释
value/cacheNames缓存的名字。必须指定至少一个,可以配置多个
例如:
@Cacheable(value={"cache1","cache2"})
key缓存的key。可以为空,如果指定要使用SpEL。默认将方法的所有参数组合起来作为key。
例如:
@Cacheable(value="cache1",key="#id")
keyGenerator定义自动生成主键的策略,使用的时候key和keyGenerator二选一
condition作缓存的条件。可以为空,使用SpEL表达式指定,返回true表示作缓存,否者不缓存。
例如:
@Cacheable(vlaue="cache",condition="#id>0")
unless也是作缓存的条件。当条件为true时,就不缓存(和condition的效果是反的)。
例如:@Cacheable(value="cache",unless="#id<0")
sync
(@Cacheable)
是否使用异步支持,这是Spring 4.3以后才支持的,默认值false,不开启异步模式
例如:
@Cacheable(value="cache",sync=true) //开启异步模式
allEntries
(@CacheEvict)
是否清空所有缓存内容。默认为false,如果指定为true,则方法调用后将立即清空所有缓存。
beforeInvocation
(@CacheEvict)
是否在方法执行前清空缓存。默认为false,如果指定为true,则方法还没有执行的时候就清空缓存。默认情况下如果方法抛出异常,就没有办法清空缓存了。

SpEL上下文数据,Spring 提供了一些供我们使用的SpEL表达式,

名称位置描述 用法示例
methodName(方法名) root对象 当前被调用的方法名 #root.methodname
method(方法) root对象 当前被调用的方法 #root.method.name
target(当前对象) root对象 当前被调用的目标对象实例 #root.target
targetClass(目标类)root对象 当前被调用的目标对象的类 #root.targetClass
args(参数列表) root对象 当前被调用的方法的参数列表 #root.args[0]
caches(缓存列表)root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name(参数名)执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result(方法返回值) 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

3、 Spring缓存的配置和使用

3.1 @Cacheable

@Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。

@Cacheable(value="user",key = "#root.methodName+'('+#id+')'")
@Override
public User get(Integer id) {
   return userMapper.selectUserById(id);
}

在CacheAspectSupport这个类的findCachedItem上打上断点,观察发下我们配置的key是有效:

3.2 自定义主键生成策略
package com.xust.iot.learningspirngbootcache01.config;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

@Configuration
public class CacheConfig {

    @Bean("userKey")  //bean的名字就是主键生成策略的名字
    public KeyGenerator keyGenerator(){
         //主键策略:方法的hashcode+@+[参数]
        return (target, method, params) -> method.getName().hashCode()+"@"+ Arrays.toString(params);
    }
}

在方法中可以这么使用:

  @Cacheable(value="user",keyGenerator = "userKey")
    @Override
    public User get(Integer id) {
        return userMapper.selectUserById(id);
    }

同样,通过打断点发现我们配置的主键生成也起作用了:

其他参数的用法可以参考下面的程序:

     /**
     *自定义缓存策略,并且只有当返回值不是null的时候才缓存
     */
    @Cacheable(value="user",keyGenerator = "userKey",condition = "#result!=null")
    @Override
    public User get(Integer id) {
        return userMapper.selectUserById(id);
    }
    
     /**
     *自定义缓存策略,并且当返回值是null的时候不缓存,也就相当于上面的写法,只不过unless是判断为false的时候才做缓存
     */
    @Cacheable(value="user",keyGenerator = "userKey",unless = "#result==null")
    @Override
    public User get(Integer id) {
        return userMapper.selectUserById(id);
    }

      /**
     *自定义缓存策略,并且开启异步缓存
     */
    @Cacheable(value="user",keyGenerator = "userKey",sync = true)
    @Override
    public User get(Integer id) {
        return userMapper.selectUserById(id);
    }
3.3 @CachePut

@CachePut注解的作用主要用于对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用 。简单来说就是用户更新缓存数据。但需要注意的是该注解的value 和 key 必须与要更新的缓存相同,也就是与@Cacheable 相同。

    //方法本调用后,总是会执行方法,返回后如果条件瞒住就来缓存
    @CachePut(value = "user",keyGenerator = "userKey",condition = "#result==true")
    @Override
    public boolean update(Integer id, User user) {
        return userMapper.updateUserById(id,user);
    }

@CachePut的其他参数的用法和一样@Cacheable参数的用法一样。这里不再赘述。

3.4 @CacheEvict

@CachEvict 的作用主要用于方法的配置,能够根据一定的条件对缓存进行清空 。


    @CacheEvict(value = "user",beforeInvocation = false)   //默认就是false,会在方法执行后清缓存
    public boolean delete(Integer id) {
        int i/0;   //这里发生异常了,缓存无法清空
        return userMapper.deleteUserById(id);
    }
   


    @CacheEvict(value = "user",beforeInvocation = true)   //在方法执行前清除缓存
    @Override
    public boolean delete(Integer id) {
        int i/0;   //即使这里会发生异常,还是会清空缓存,因为清除缓存是在方法执行前执行的
        return userMapper.deleteUserById(id);
    }

     @CacheEvict(value = "user",allEntries = true)   //方法调用后清空所有缓存
    @Override
    public boolean delete(Integer id) {
        return userMapper.deleteUserById(id);
    }

    @CacheEvict(value = "user",allEntries = false)   //默认不清空所有缓存
    @Override
    public boolean delete(Integer id) {
        return userMapper.deleteUserById(id);
    }

3.5 @Caching

有时候我们可能组合多个Cache注解使用,此时就需要@Caching组合多个注解标签了。

  //组合缓存策略,Caching只有下面三个属性
 @Caching(cacheable = {
            @Cacheable(value = "user", keyGenerator = "userKey")
    },
            put = {
           @CachePut(value = "user", keyGenerator = "userKey")
    },
            evict = {
           @CacheEvict(beforeInvocation = true, allEntries = true)
            }
    )
    @Override
    public boolean delete(Integer id) {
        return userMapper.deleteUserById(id);
    }
3.6 @CachingConfig

当我们需要缓存的地方越来越多,你可以使用@CacheConfig(cacheNames = {“cacheName”})注解来统一指定value/cacheNames的值,这时可省略value/cacheNames,如果你在你的方法依旧写上了value/cacheNames,那么依然以方法的@CacheConfig配置的值为准。使用方法如下:

@CacheConfig(cacheNames = "user")   //在类名头上只用@CacheConfig来指定这个类全局的缓存的名字
@Service
public class UserServiceImpl implements IUserService<User> {

    @Autowired
    UserMapper userMapper;

    @Cacheable(keyGenerator = "userKey", sync = true)
    @Override
    public User get(Integer id) {
        return userMapper.selectUserById(id);
    }

    @CachePut(value = "user", keyGenerator = "userKey", condition = "#result==true")
    @Override
    public boolean update(Integer id, User user) {
        return userMapper.updateUserById(id, user);
    }
}

@CacheConfig可以配置单的属性如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {

	String[] cacheNames() default {};    //缓存名字,它里面没有value

	String keyGenerator() default "";    //主键生成策略
  
	String cacheManager() default "";     //指定缓存管理器

	String cacheResolver() default "";    //缓存处理器
}

留言区

还能输入500个字符