SpringMvc in Action——缓存数据

小孩子常常会反复问我一个问题:“为什么你长的这么帅啊?”过了一会,又再问一遍。
很多方面来看,在我们所编写的应用中,有些组件也是这样的。无状态的组件一般来讲扩展性要好一些,但是他们也更倾向于一遍一遍询问相同的问题。因为他们是无状态的,一旦完成当前的任务,就会丢弃掉已经获取到的所有解答。
为了得到问题的答案,我们可能会使用数据库,调用远程服务,或者执行复杂的计算。
而如果问题的答案变更没有那么频繁或者根本没有变化,那么再去走一遍流程是很浪费的,所以我们还不如将问题的答案记住,这就用到了缓存Caching。

启用对缓存的支持

Spring对缓存的支持有两种方式:

  • 注解驱动的缓存
  • XML声明的缓存

在使用Spring的缓存抽象时,最为通用的方法是加上CacheableCacheEvict注解,我们对XML声明的可以有所了解。在往bean上添加缓存注解之前,必须启用Spring对注解驱动缓存的支持。我们可以在配置类上添加@EnableCaching,这样的话就能启用注解驱动的缓存。

package spittr.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager(){
        return new ConcurrentMapCacheManager();
    }
}

@EnableCaching启动缓存。
cacheManager()用以声明缓存管理器。

如果以XML的方式配置,那么可以用<cache:annotation-driven>元素来启动注解驱动的缓存。
在这里插入图片描述
本质上,@EnableCaching<cache:annotation-driven>的工作方式是相同的。它们都会创建一个切面并出发Spring缓存注解的切点,根据所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存之中移除某个值。
不仅启动了注解驱动的缓存,而且还声明了一个缓存管理器(cache manager)的bean。缓存管理器是Spring缓存抽象的核心,它能够与多个流行的缓存实现进行继承。
本例中声明的ConcurrentMapCacheManager,这个简单的缓存管理器使用java.util.concurrent.ConcurrentHashMap作为其缓存。它非常简单,因此对于开发,测试或基础的应用来讲,这是一个很不错的选择。但对于生产级别的大型企业级程序,这可能不是很好的选择。

配置缓存管理器
Spring3.1内置了五个缓存管理器:
在这里插入图片描述
Spring3.2引入了另外一个缓存管理器。除了核心的Spring框架,Spring Data又提供了两个缓存管理器:

  • RedisCacheManager
  • GemfireCacheManager

可以看到,这么多缓存管理器,我们有很多选择。尽管做出的选择会影响到如何缓存,但是Spring声明缓存的方式并没有什么差别。

ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。
redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。
如果是单个应用或者对缓存访问要求很高的应用,用ehcache。
如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。


使用Ehcache缓存:

package spittr.config;

import net.sf.ehcache.management.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public EhCacheCacheManager cacheManager(CacheManager cacheManager){
        return new EhCacheCacheManager(cacheManager);
    }

    @Bean
    public EhCacheManagerFactoryBean ehcache(){
        EhCacheManagerFactoryBean ehCacheManagerFactoryBean=
                new EhCacheManagerFactoryBean();
        ehCacheManagerFactoryBean.setConfigLocation(
                new ClassPathResource("ehcache.xml"));
        return ehCacheManagerFactoryBean;
    }
}

这是一个基础的EhCache配置,其他的配置细节,可以等具体实现的时候再了解。


使用Redis缓存:
其实缓存的条目无非就是一个键值对,很自然的想到Redis缓存。
Redis可以用来为Spring缓存抽象机制存储缓存条目。RedisCacheManager是一个CacheManager的实现。RedisCacheManager会与一个Redis服务器写作,并通过RedisTemplate将缓存条目存储到Redis中。
为了使用RedisCacheManager,我们需要RedisTemplate bean以及RedisConnectionFactory实现类的一个bean.

在RedisTemplate就绪之后,配置RedisCacheManager就是非常简单的一件事了:

package spittr.config;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
@EnableCaching
public class RedisCacheConfig {
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate){
        return new RedisCacheManager(redisTemplate);
    }

    @Bean
    public JedisConnectionFactory jedisConnectionFactory(){//redisFactory
        JedisConnectionFactory jedisConnectionFactory=new JedisConnectionFactory();
        jedisConnectionFactory.afterPropertiesSet();
        return jedisConnectionFactory;
    }

    @Bean
    public RedisTemplate<String,String> redisTemplate(
            RedisConnectionFactory redisConnectionFactory
    ){
        RedisTemplate<String,String> redisTemplate=new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

在配置完缓存管理器并启动缓存后,就可以在bean方法上应用缓存规则了。

为方法添加注解以支持缓存

在这里插入图片描述
填充缓存
我们可以看到@Cacheable@CachePut注解都可以填充缓存,但是他们的工作方式略有差异。
@Cacheable首先在缓存中查找条目,如果找到了匹配的条目,那么就不会对方法进行调用了。如果没有找到匹配的条目,方法会被调用并且返回值放到缓存之中。
@CachePut并不会在缓存中检查匹配的值,目标方法总是会调用,并将返回值添加到缓存中。
他们俩属性有一些是共有的:
在这里插入图片描述
在最简单的情况下,只需要使用value属性指定一个或多个缓存即可。例如考虑SpittleRepository中的findOne()方法。在初始保存之后,Spittle就不会再发生变化了。如果有的Spittle比较热门并且会被频繁的请求,我们可以在findOne()上添加@Cacheable注解,确保将Spittle保存在缓存中,从而避免对数据库的不必要的访问。

    @Cacheable("spittleCache")
    public Spittle findOne(long id) {
        return jdbc.queryForObject(
                "select id, message, created_at, latitude, longitude" +
                        " from Spittle" +
                        " where id = ?",
                new SpittleRowMapper(), id);
    }

使用@Cacheable(“spittleCache”),当findOne()被调用时,缓存切面会拦截调用并在缓存中查找之前以名"spittleCache"存储的返回值,缓存的Key是传递到findOne方法中的id参数。

当然,可以把@Cacheable放在接口的方法上面,而不是放在实现的方法上面。这样,接口的方法的所有实现都会实现缓存。

将值放到缓存之中
@CachePut采用了一种更为直接的流程。带有这个注解的方法始终会被调用,而且它的返回值也会放到缓存中。这提供了很便利机制,让我们在请求之前预先加载缓存。
例如,当一个全新的Spittle通过SpittleRepository的save()方法保存之后,很可能马上就会请求这条记录。所以当save()方法调用后,立即将Spittle塞到缓存中是很有意义的。当其他人通过findOne()查找时,它已经准备就绪。

@Cacheable("spittleCache")
Spittle save(Spittle spittle);

这里唯一的问题就是,缓存的key。前文说过,缓存的key是基于方法的参数决定的。因为save的参数是Spittle,那么它会做缓存的key。然而,诡异的是,Spittle的键值对都是spittle,更不幸的是,我们希望用id作为他的key。
让我们看一下怎么自定义缓存的key。
自定义缓存Key
@Cacheable@CachePut都有一个名为key属性,这个属性能够替换默认的key,它是通过一个SpEL表达式计算得到的。
具体到我们现在的场景,我们需要将Key设置为所保存Spittle的ID,以参数形式传递给save()返回的Spittle得到的id属性。幸好,为缓存编写SpEL表达式的时候,Spring暴露了一些很有用的元数据。
在这里插入图片描述
对于Save()方法, 我们需要的键是所返回的Spittle对象的id,表达式#result能够得到方法调动的返回值Spittle对象。我们可以将key属性设置为#result.id来引用id属性。

@Cacheable(value="spittleCache",key="#result.id")
Spittle save(Spittle spittle);

条件化缓存
通过为方法添加Spring的缓存注解,Spring就会围绕这个方法创建一个缓存切面。但是,在有些场景下我们可能希望将缓存功能关闭。
@Cacheable@CachePut提供了两个属性以实现条件化缓存:unless和condition。这两个属性都接受一个SpEL表达式。如果unless属性的SpEL表达式计算结果为true,那么缓存方法返回的数据就不会放到缓存中。与之类似,如果condition计算结果为false,那么这个方法缓存就会被禁用掉。

表面上看unless和condition都是做的相同的事情,但是实际上,unless属性只能组织对象放进缓存,但是在这个方法调用的时候,依然先去缓存中查找,没找到才调用方法。而condition的表达结果为false时,不会去缓存中查找,直接调用方法,同时返回值也不会放进缓存中。

作为样例(尽管有些牵强),假设对于message属性包含"NoCache"的Spittle对象不进行缓存。为了阻止这样的对象被缓存起来,我们使用unless:

@Cacheable(value="spittleCache"
	unless="#result.message.contains('NoCache')")
Spittle findOne(long id);

假如我们对于ID小于10的Spittle对象不使用缓存,也不希望从缓存中获取数据:

@Cacheable(value="spittleCache"
	unless="#result.message.contains('NoCache')"
	condition="#id>=10")
Spittle findOne(long id);

移除缓存条目

@CacheEvict并不往缓存中添加任何东西,相反,它还会移除一个或更多的在缓存中的条目。
在什么场景需要从缓存中移除内容呢?当缓存值不再合法时,我们应该确保将其从缓存中移除,这样的话,后续的缓存命中就不会返回旧的或者已经不存在的值。

这样的话,SpittleRepository的remove()方法就是使用@CacheEvict的绝佳选择:

@CacheEvict("spittleCache")
void remove(long spittleId);

在这里插入图片描述

使用XML声明缓存

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值