一定要把这张图理解的非常透彻,才能把缓存注解用好。
仍然以我们之前一直使用的ArtivleServiceImpl为例(包含增删改查方法),添加缓存注解。
被@Cacheable
注解的方法,在第一次被请求的时候执行方法体,并将方法的返回值放入缓存。在第二次请求的时候,由于缓存中已经包含该数据,将不执行被注解的方法的方法体,直接从缓存中获取数据。对于查询过程的缓存操作,要满足上图中的蓝色箭头线指引的操作流程,所有的操作流程只需要加上一个@Cacheable
就可以实现。
public static final String CACHE_OBJECT = "article"; //缓存名称
@Cacheable(value = CACHE_OBJECT,key = "#id") //这里的value和key参考下面的redis数据库截图理解
public ArticleVO getArticle(Long id) {
return dozerMapper.map(articleMapper.selectById(id),ArticleVO.class);
}
需要注意的是:缓存注解的key是一个SPEL表达式,“#id”表示获取函数的参数id的值作为缓存的key值。如果参数id=1,那么最终redis缓存的key就是:“article::1”。下图是redis缓存数据库中这条缓存记录的截图:
大家要注意Object
和List<Object>
是两种不同的业务数据,所以对应的缓存也是两种缓存。注意下文中,缓存注解的key是字符串list,因为缓存注解默认使用SPEL表达式,如果我们想使用字符串需要加上斜杠。
public static final String CACHE_LIST_KEY = "\"list\"";
@Cacheable(value = CACHE_OBJECT,key = CACHE_LIST_KEY)
public List<ArticleVO> getAll() {
List<Article> articles = articleMapper.selectList(null);
return DozerUtils.mapList(articles,ArticleVO.class);
}
对于查询过程的缓存操作,要满足上图中的蓝色箭头线指引的操作流程,所有的操作流程只需要加上一个@Cacheable
就可以实现。目前MySQL数据库的article表有4条数据,所以缓存结果是一个包含4个article元素的数组
如下面的代码所示,将在函数执行成功之后删除redis key为“article::1”的缓存(假设删除id=1的记录)。
@Override
@Caching(evict = {
@CacheEvict(value = CACHE_OBJECT,key = CACHE_LIST_KEY), //删除List集合缓存
@CacheEvict(value = CACHE_OBJECT,key = "#id") //删除单条记录缓存
})
public void deleteArticle(Long id) {
articleMapper.deleteById(id);
}
@CacheEvict
,所以我们用@Caching
注解把两个@CacheEvict
包起来。@CacheEvict(value = CACHE_OBJECT,key = CACHE_LIST_KEY) //删除List集合缓存
public void saveArticle(ArticleVO article) {
Article articlePO = dozerMapper.map(article, Article.class);
articleMapper.insert(articlePO);
}
执行完成上面的方法,MySQL数据库新增了一条article记录;成功之后在《1.2.集合对象的查询缓存》缓存到redis中的key为“article::list”的缓存也将被删除。
注意更新对象的时候,我们在该方法上面加了两个缓存注解。
key = "#article.getId()"
表示使用参数article的id属性作为缓存key。@CachePut(value = CACHE_OBJECT,key = "#article.getId()")
@CacheEvict(value = CACHE_OBJECT,key = CACHE_LIST_KEY)
public ArticleVO updateArticle(ArticleVO article) {
Article articlePO = dozerMapper.map(article,Article.class);
articleMapper.updateById(articlePO);
return article; //为了保证一致性,最后返回的更新结果,最好从数据库去查
}
执行完成该方法,假如ArticleVO参数对象的id=1
需要特别注意的是:如果在更新方法上使用CachePut注解,该方法一定要有数据更新之后返回值,因为返回值就是缓存值。比较简单的做法是直接将不一致的缓存删掉,而不是去更新缓存。这样操作对于程序员的要求更低,不容易出错。缓存数据可以没有,但是不能和后端被缓存的关系数据库数据不一致。
@Override
@Caching(evict = {
@CacheEvict(value = CACHE_OBJECT,key = CACHE_LIST_KEY), //删除List集合缓存
@CacheEvict(value = CACHE_OBJECT,key = "#article.getId()") //删除单条记录缓存
})
public void updateArticle(ArticleVO article) {
Article articlePO = dozerMapper.map(article,Article.class);
articleMapper.updateById(articlePO);
}
方法不需要有返回值。执行完成该方法,假如ArticleVO参数对象的id=1
@Cacheable 通常应用到读取数据的查询方法上:先从缓存中读取,如果没有再调用方法获取数据,然后把数据查询结果添加到缓存中。如果缓存中查找到数据,被注解的方法将不会执行。
@Cacheable 主要的参数 | 参数说明 | 示例 |
---|---|---|
value(cacheNames) | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个。当value为数组的时候会针对同一条记录做多个缓存。 | 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}) |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
@CachePut通常应用于修改方法配置,能够根据方法的请求参数对其注解的函数返回值进行缓存,和 @Cacheable 不同的是,它每次都会触发被注解方法的调用。
@CachePut 主要的参数 | 参数说明 | 示例 |
---|---|---|
value(cacheNames) | 缓存的名称,在 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=”#userName.length()>2”) |
@CachEvict 通常应用于删除方法配置,能够根据一定的条件对缓存进行删除。可以清除一条或多条缓存。
@CacheEvict 主要的参数 | 参数说明 | 示例 |
---|---|---|
value(cacheNames) | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @CachEvict(value=”mycache”) 或者 @CachEvict(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @CachEvict(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存 | 例如: @CachEvict(value=”testcache”, condition=”#userName.length()>2”) |
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | 例如: @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | 例如:@CachEvict(value=”testcache”,beforeInvocation=true) |
在实际的生产环境中,没有一定之规,哪种注解必须用在哪种方法上,@CachEvict 注解通常也用于更新方法上。数据的缓存策略,要根据资源的使用方式,做出合理的缓存策略规划。保证缓存与业务数据库的数据一致性。并做好测试,对于缓存的正确使用,测试才是王道!