3

spring缓存

org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口是spring中用来统一不同的缓存技术公用接口

  1. Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
  2. Cache接口下spring提供了各种xxxCache的实现,如 RedisCache、ConcurrentMapCache等
  3. 每次调用有缓存功能的方法时,spring 会检查指定参数的目标方法是否已经被调用过,如果有就直接从缓存中获取方法的结果;如果没有就调用过方法就先缓存结果后在将结果返回给用户,下次调用直接从缓存中获取。
  4. 使用spring缓存抽象时我们需要关注以下两点:

    • 确定方法需要被缓存以及他们的缓存策略
    • 从缓存中读取之前缓存存储的数据

spring 缓存中几个概念

名词 说明
Cache 缓存接口,定义缓存操作。实现有: 等
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存的注解
@CachePut 保证方法被调用,又希望结果被缓存,通常用于缓存更新
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略

@Cacheable、@CacheEvict和@CachePut 注解属性

属性 说明 示例
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=”#userNam e.length()>2”)
allEntries (@CacheEvict ) 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation(@CacheEvict) 是否在方法执行前就清空,缺省为 false,如果指定 为 true,则在方法还没有执行的时候就清空缓存, 缺省情况下,如果方法执行抛出异常,则不会清空 缓存 @CachEvict(value=”testcache”, beforeInvocation=true)
unless(@CachePut) (@Cacheable) 用于否决缓存的,不像condition,该表达式只在方 法执行之后判断,此时可以拿到返回值result进行判 断。条件为true不会缓存,fasle才缓存 @Cacheable(value=”testcache”,unless=”#result == null”)

SpEL 表达式

作用 表达式
当前被调用的方法名 #root.methodName
当前被调用的方法 #root.method.name
当前被调用的目标对象 #root.target
当前被调用的目标对象类 #root.targetClass
当前被调用的方法的参数列表 #root.args[0]
方法参数的名字 可以直接 #参数名,也可以使用 #p0或#a0,0代表参数的索引
方法执行后的返回值 #result

spring 缓存使用

  • 引入 starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  • 配置注解 @EnableCaching
@EnableCaching
@SpringBootApplication
public class SpringBootCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootCacheApplication.class, args);
    }
}
  • 使用缓存注解 @Cacheable、@CacheEvict和@CachePut
    @Cacheable(cacheNames = {"emp"})
    public Emp queryEmp(Integer id) {
        System.out.println("查询员工ID: " + id);
        Emp emp = empMapper.queryEmpById(id);
        return emp;
    }

当我们在浏览器输入 http://localhost:8080/query/1时,控制台日志会打印日志

clipboard.png

说明此时查询成功,但是当我再次刷新页面时控制台不打印日志,单页面能正常得到结果,说明缓存起作用了。

@Cacheable

将方法的结果进行缓存,以后在需要数据时,直接在缓存中获取,不会再调用方法

@Cacheable的几个属性

  • cacheNames : 缓存的名字
  • key : 该 cacheNames 缓存中的使用的key值,不指定时默认使用方法的参数,也可以使用 sepl 表达式来指定其值,比如:key="#id" 表示 queryEmp 方法的参数 id 的值,同时 "#id" 等价于 "#"a0、"#p0"、#root.args[0]
  • keyGenerator : 指定key的生成器。key / keyGenerator 二选一使用,不要同时使用
  • cacheManager : 指定从哪个缓存管理器中取,比如在 RedisCache 还是ConcurrentMapCache 中取值
  • condition : 指定缓存条件,满足某些条件后才缓存方法结果,也可以是 spel 表达式比如:condition = "#id > 0",当第一个参数值大于1时才缓存
  • unless : 否定缓存,当 unless 指定的条件返回 true 是,方法结果就不会被缓存比如:unless = "#result == null" 意思是方法返回结果为空,不进行缓存

缓存的工作原理

  • 自动配置类 CacheAutoConfiguration
  • 通过 CacheConfigurationImportSelector 类给容器中配置了其他的缓存配置,比如:
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.SimpleCacheConfiguration
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

默认使用 SimpleCacheConfiguration,它的作用是什么呢?

@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 this.customizerInvoker.customize(cacheManager);
    }
}

可以看到给容器中注入了一个 ConcurrentMapCacheManager 的缓存管理器。在 ConcurrentMapCacheManager 中我发现一个 ConcurrentHashMap,由此可以知道spring默认缓存的本质利用的map来缓存的。


public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
}

@CachePut

调用注解的方法,又去更新缓存数据。使用场景,修改数据库的某些数据的同时更新缓存数据。这个注解的运行时机先运行目标方法,再将结果缓存起来。

@CachePut(value = "emp",key = "#emp.id")
public Employee updateEmp(Emp emp){
      System.out.println("updateEmp:"+emp);
      empMapper.updateEmp(employee);
      return emp;
}

@CacheEvict

清除缓存中的数据

   /**
     * @CacheEvict:缓存清除
     *  key:指定要清除的数据
     *  allEntries = true:指定清除这个缓存中所有的数据
     *  beforeInvocation = false:缓存的清除是否在方法之前执行
     *      默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
     *
     *  beforeInvocation = true:
     *      代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
     *
     *
     */
    @CacheEvict(value="emp",beforeInvocation = true/*key = "#id",*/)
    public void deleteEmp(Integer id){
        System.out.println("deleteEmp:"+id);
        empMapper.deleteEmpById(id);
    }

@Caching 定义复杂的缓存规则

@Caching(
     cacheable = {
         @Cacheable(value="emp",key = "#lastName")
     },
     put = {
         @CachePut(value="emp",key = "#result.id"),
         @CachePut(value="emp",key = "#result.email")
    }
)
public Employee getEmpByLastName(String lastName){
        return empMapper.getEmpByLastName(lastName);
}

这段代码的意思是:我们根据lastName查询的结果会放到缓存中,同时也会以id,email为key来缓存数据的。但是需要注意一点,使用lastName查询时,方法还是会执行,因为我们使用@CachePut注解,这个注解要求目标方法一定要执行的。

@CachingConfig

抽象公共的缓存配置,什么意思?比如我们案例中所有的缓存都是emp,就是value='emp'这个指定,那么每个方法都指定很费劲,此时可以使用 @CachingConfing注解

@CacheConfig(cacheNames="emp")
@Service
public class EmployeeService {

}

使用缓存中间件 redis

  • 引入 starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId></dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  • 修改 yml 配置文件
spring:
  redis:
    host: 127.0.0.1

redis 使用

RedisAutoConfiguration类中帮我自动注入了RedisTemplateStringRedisTemplate,也就是说我们可以直接使用它们进行redis的日常操作了,一般使用RedisTemplate就可以了。

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootCacheApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Test
    public void testSetRedis() {
        Emp emp = new Emp();
        emp.setId(4L);
        emp.setName("kate");
        emp.setDeptId(1L);
        emp.setEmail("SAJJee@qq.com");
        emp.setSex(0);
        redisTemplate.opsForValue().set("emp:4", emp);
        
        //其他常用的操作函数
        //redisTemplate.opsForHash();
        //redisTemplate.opsForList();
        //redisTemplate.opsForSet();
        //redisTemplate.opsForZSet();
    }
}

redis 数据存储序列化问题

org.springframework.data.redis中提供的序列化方式有

  • GenericJackson2JsonRedisSerializer
  • GenericToStringSerializer
  • Jackson2JsonRedisSerializer
  • JdkSerializationRedisSerializer
  • OxmSerializer
  • StringRedisSerializer

使用默认的序列化方式:

    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void testSetRedis() {

        Emp emp = new Emp();
        emp.setId(4L);
        emp.setName("kate");
        emp.setDeptId(1L);
        emp.setEmail("SAJJee@qq.com");
        emp.setSex(0);
        redisTemplate.opsForValue().set("emp:4", emp);  
    }

执行结果:

clipboard.png

自定义序列化:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Emp> empRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<String, Emp> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer<Emp> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Emp.class);
        //设置key的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式
        template.setValueSerializer(jsonRedisSerializer);
        return template;
    }
}

测试用例:

    //注入自定义序列化方式模板
    @Autowired
    private RedisTemplate<String, Emp> empRedisTemplate;
    @Test
    public void testSetRedis() {

        Emp emp = new Emp();
        emp.setId(4L);
        emp.setName("阿江");
        emp.setDeptId(1L);
        emp.setEmail("SAJJee@qq.com");
        emp.setSex(0);
        empRedisTemplate.opsForValue().set("emp:4", emp);//操作字符串
    }

执行结果:

clipboard.png


一只小小鸟
144 声望25 粉丝

如何做一个深层次的思考者,从简单开始、从记录开始。