Spring 缓存

Spring缓存 spring都有哪几种方式能实现缓存?为什么以及什么情况下需要用到、更新、删除缓存 TOP

启用支持

1:注解驱动的缓存 @Cacheable @CacheEvict
2:xml声明的缓存
如果使用java配置,需要在其中一个配置类上添加@EnableCaching

@EnableCaching
@Configuration
public class CacheConfig {
    @Bean
    // 声明缓存管理器
    public CacheManager cacheManager(){
        return new ConcurrentMapCacheManager();
    }
}

如果使用xml配置 (启用缓存)
本质上,都是创建一个切面并触发spring缓存注解的切点。根据所使用的注解以及缓存状态,这个切面会从缓存获取数据,将数据添加到缓存或从缓存中移除
ConcurrentMapCacheManager这个简单的缓存管理器使用java.util.concurrent.ConcurrentHashMap作为缓存存储(测试,基于内存)

  • 配置缓存管理器
    SimpleCacheManager
    NoOpCacheManager
    ConcurrentMapCacheManager
    CompositeCacheManager
    EhCacheCacheManager
    RedisCacheManager(来自于Spring Data Redis项目)
    GemfireCacheManager(来自于Spring Data GemFire项目)

1:使用ehcache

EhcacheConfig.class
    /**
     * 声明一个CacheManager bean
     * Spring提供了EhCacheManager-FactoryBean来生成EhCache的CacheManager
     *
     */
    @Bean
    public EhCacheManagerFactoryBean ehcache(){
        EhCacheManagerFactoryBean eb = new EhCacheManagerFactoryBean();
        eb.setConfigLocation(new ClassPathResource("spittr/cache/ehcache.xml"));
        return eb;
    }
    /**
     * 注:
     * org.springframework.cache.ehcache
     * 需要导入spring-context-support,且还需要额外导入net.sf.ehcache
     * 通过传入EhcacheCacheManager实例实现
     * EhCache的CacheManager要被注入到Spring的EhCacheCacheManager
     *
     */
    @Bean
    public EhCacheCacheManager cacheManager(CacheManager cm){
        return new EhCacheCacheManager(cm);
    }
ehcache.xml:
<ehcache>
    <cache name="spittleCache" maxBytesLocalHeap="50m" timeToLiveSeconds="100"/>
</ehcache>

2:使用redis缓存
如果缓存的条目是键值对形式的,那非常适合用redis存储

需要引入org.springframework.data.reids & redis.client
redis提供了RedisCacheManager,是CacheManager的实现,会与redis服务器协作,通过RedisTemplate将缓存条目存储到Redis中
    @Bean
    public JedisPoolConfig jedisPoolConfig(){
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxWaitMillis(1000);
        return jedisPoolConfig;
    }
    @Bean
    public JedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig){
        JedisConnectionFactory jf = new JedisConnectionFactory();
        jf.setPoolConfig(jedisPoolConfig);
        jf.setHostName("localhost");
        jf.setPort(6379);
        jf.afterPropertiesSet();
        return jf;
    }
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate){
        return new RedisCacheManager(redisTemplate);
    }
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
注:报错// DefaultSerializer requires a Serializable payload but received an object of type [spitter.Spittle]
对应类必须实现Serializable

为方法添加注解支持缓存

@Cacheable 表明Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存的值。否则的话,这个方法就会被调用,返回值会放到缓存之中
@CachePut 表明Spring应该将方法的返回值放到缓存中。在方法的调用前并不会检查缓存,方法始终都会被调用
@CacheEvict 表明Spring应该在缓存中清除一个或多个条目
@Caching 这是一个分组的注解,能够同时应用多个其他的缓存注解

  • 填充缓存
    /**
    * 缓存切面会拦截调用并在缓存中查找之前以名spittleCache存储的返回值
    * 缓存的key是传递到findOne()方法中的id参数
    * 如果按照这个key能够找到值的话,就会返回找到的值,方法不会再被调用
    * 但是这样仅限于这个实现类,为了避免出现这个问题,可以将Cacheable声明在接口方法上
    * 带有@CachePut的方法始终会被调用,可以实现预加载
    * 例如,可以在save(Spittle spittle)方法上添加@CachePut("spittleCache")
    * 但是这样回导致key为Spittle,所以需要指定key
    * @CachePut("spittleCache", key="#result.id") <= 表达式#result能够得到返回的Spittle
    * 此外,还有当数据有修改时,可以通过CachePut强制更新
    */
    @Cacheable("spittleCache")
    public Spittle findSpittels(int id) {
        System.out.println("调用一次:" + id);
        return jdbc.queryForObject("select message, created_at, longitude,latitude from Spittle where id = ?",
                new SpitterRowMapper(),
                id);
    }
注:这里遇到一个问题,1.7.0的spring-data-redis返回RedisCacheManager对象,其父类没有实现CacheManager接口,是因为没有spring-support的jar或者对应jar的版本与spring版本不匹配
  • 条件化缓存
    某些情况下,可能只有符合某些条件下我们才会使用缓存
    /**
    * unless:如果为true,阻止将对象放入缓存,但是如果缓存中存在,依然从缓存中读取
    * condition:如果condition返回false,禁用缓存
    */
    @CachePut(value = "spitterCache", key = "#result.username", unless = "#result.username.contains('NoCache')")