点击上方“芋道源码”,选择“设为星标”
做积极的人,而不是积极废人!
源码精品专栏
摘要: 原创出处 http://www.iocoder.cn/Spring-Boot/Cache/ 「芋道源码」欢迎转载,保留摘要,谢谢!
1. 概述
2. 注解
3. Spring Boot 集成
4. Ehcache 示例
5. Redis 示例
666. 彩蛋
1. 概述
在系统访问量越来越大之后,往往最先出现瓶颈的往往是数据库。而为了减少数据库的压力,我们可以选择让产品砍掉消耗数据库性能的需求。当然,我们也可以选择如下方式优化:
艿艿:在这里,我们暂时不考虑优化数据库的硬件,索引等等手段。
读写分离。通过将读操作分流到从节点,避免主节点压力过多。
分库分表。通过将读写操作分摊到多个节点,避免单节点压力过多。
缓存。相比数据库来说,缓存往往能够提供更快的读性能,减小数据库的压力。关于缓存和数据库的性能情况,可以看看如下两篇文章:
-
《性能测试 —— Redis 基准测试》
《性能测试 —— MySQL 基准测试》
那么,在引入缓存之后,我们的读操作的代码,往往代码如下:
// UserService.java
@Autowired
private UserMapper userMapper; // 读取 DB
@Autowired
private UserCacheDao userCacheDao; // 读取 Cache
public UserDO getUser(Integer id) {
// 从 Cache 中,查询用户信息
UserDO user = userCacheDao.get(id);
if (user != null) {
return user;
}
// 如果 Cache 查询不到,从 DB 中读取
user = userMapper.selectById(id);
if (user != null) { // 非空,则缓存到 Cache 中
userCacheDao.put(user);
}
// 返回结果
return user;
}
这段代码,是比较常用的缓存策略,俗称**“被动写”**。整体步骤如下:
-
1)首先,从 Cache 中,读取用户缓存。如果存在,则直接返回。
2)然后,从 DB 中,读取用户数据。如果存在,写入 Cache 中。
3)最后,返回 DB 的查询结果。
可能会有胖友说,这里没有考虑缓存击穿、缓存穿透、缓存并发写的情况。恩,是的,但是这并不在本文的内容范围。感兴趣的,可以看看我的男神超哥写的 《缓存穿透、缓存并发、缓存失效之思路变迁》 文章。嘿嘿~
虽然说,上述的代码已经挺简洁了,但是我们是热爱“偷懒”的开发者,必然需要寻找更优雅(偷懒)的方式。在 Spring 3.1 版本的时候,它发布了 Spring Cache 。关于它的介绍,如下:
FROM 《注释驱动的 Spring Cache 缓存介绍》
Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。
Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache 集成。
其特点总结如下:
通过少量的配置 annotation 注释即可使得既有代码支持缓存
支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存
支持 Spring Express Language,能使用对象的任何属性或者方法来定义缓存的 key 和 condition
支持 AspectJ,并通过其实现任何方法的缓存支持
支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性
介绍有点略长,胖友耐心看看噢。
简单来说,我们可以像使用
@Transactional声明式事务,使用 Spring Cache 提供的@Cacheable等注解,???? 声明式缓存。而在实现原理上,也是基于 Spring AOP 拦截,实现缓存相关的操作。
下面,我们使用 Spring Cache 将 #getUser(Integer id) 方法进行简化。代码如下:
// UserService.java
public UserDO getUser2(Integer id) {
return userMapper.selectById(id);
}
// UserMapper.java
@Cacheable(value = "users", key = "#id")
UserDO selectById(Integer id);
在 UserService 的
#getUser2(Integer id)方法上,我们直接调用 UserMapper ,从 DB 中查询数据。在 UserMapper 的
#selectById(Integer id)方法上,有@Cacheable注解。Spring Cache 会拦截有@Cacheable注解的方法,实现“被动写”的逻辑。
是不是瞬间很清爽。下面,让我们开始愉快的入门吧。
2. 注解
在入门 Spring Cache 之前,我们先了解下其提供的所有注解:
@Cacheable@CachePut@CacheEvict@CacheConfig@Caching@EnableCaching
2.1 @Cacheable
@Cacheable 注解,添加在方法上,缓存方法的执行结果。执行过程如下:
1)首先,判断方法执行结果的缓存。如果有,则直接返回该缓存结果。
2)然后,执行方法,获得方法结果。
3)之后,根据是否满足缓存的条件。如果满足,则缓存方法结果到缓存。
4)最后,返回方法结果。
@Cacheable 注解的常用属性,如下:
cacheNames属性:缓存名。必填。[]数组,可以填写多个缓存名。values属性:和cacheNames属性相同,是它的别名。key属性:缓存的 key 。允许空。-
如果为空,则默认方法的所有参数进行组合。
如果非空,则需要按照 SpEL(Spring Expression Language) 来配置。例如说,
@Cacheable(value = "users", key = "#id"),使用方法参数id的值作为缓存的 key 。
condition属性:基于方法入参,判断要缓存的条件。允许空。-
如果为空,则不进行入参的判断。
如果非空,则需要按照 SpEL(Spring Expression Language) 来配置。例如说,
@Cacheable(condition="#id > 0"),需要传入的id大于零。
unless属性:基于方法返回,判断不缓存的条件。允许空。-
如果为空,则不进行入参的判断。
如果非空,则需要按照 SpEL(Spring Expression Language) 来配置。例如说,
@Cacheable(unless="#result == null"),如果返回结果为null,则不进行缓存。要注意,
condition和unless都是条件属性,差别在于前者针对入参,后者针对结果。
@Cacheable 注解的不常用属性,如下:
keyGenerator属性:自定义 key 生成器 KeyGenerator Bean 的名字。允许空。如果设置,则key失效。cacheManager属性:自定义缓存管理器 CacheManager Bean 的名字。允许空。一般不填写,除非有多个 CacheManager Bean 的情况下。cacheResolver属性:自定义缓存解析器 CacheResolver Bean 的名字。允许空。sync属性,在获得不到缓存的情况下,是否同步执行方法。-
默认为
false,表示无需同步。如果设置为
true,则执行方法时,会进行加锁,保证同一时刻,有且仅有一个方法在执行,其它线程阻塞等待。通过这样的方式,避免重复执行方法。注意,该功能的实现,需要参考第三方缓存的具体实现。
2.2 @CachePut
@CachePut 注解,添加在方法上,缓存方法的执行结果。不同于 @Cacheable 注解,它的执行过程如下:

本文介绍了Spring Boot中的缓存管理,包括Spring Cache的注解使用,如@Cacheable、@CachePut和@CacheEvict,以及Ehcache和Redis的集成示例。通过注解实现缓存的读写和删除,帮助减少数据库压力,提高系统性能。
最低0.47元/天 解锁文章
2685

被折叠的 条评论
为什么被折叠?



