一:为什么需要缓存
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
使用缓存是一个很“高性价比”的性能优化方式,尤其是对于有大量重复查询的程序来说。通常来说,在WEB后端应用程序来说,
耗时比较大的往往有两个地方:
1、查数据库;
2、调用其它服务的API(因为其它服务最终也要去做查数据库等耗时操作);
重复查询也有两种:
1、我们在应用程序中代码写得不好,写的for循环,可能每次循环都用重复的参数去查询了。
2、大量的相同或相似请求造成的。比如资讯网站首页的文章列表、电商网站首页的商品列表、微博等社交媒体热搜的文章等等,当大量的用户都去请求同样的接口,同样的数据,如果每次都去查数据库,那对数据库来说是一个不可承受的压力。所以我们通常会把高频的查询进行缓存,我们称它为“热点”
二:SpringCache介绍
存有诸多的好处,于是大家就摩拳擦掌准备给自己的应用加上缓存的功能。但是网上一搜却发现缓存的框架太多了,各有各的优势,比如==Redis[中央缓存-远程缓存]==、Memcached、Guava、Caffeine[本地缓存]等等。
如果我们的程序想要使用缓存,就要与这些框架耦合。聪明的架构师已经在利用接口来降低耦合了,利用面向对象的抽象和多态的特性,做到业务代码与具体的框架分离。
但我们仍然需要显式地在代码中去调用与缓存有关的接口和方法,在合适的时候插入数据到缓存里,在合适的时候从缓存中读取数据。想一想「AOP」的适用场景,这不就是天生就应该AOP去做的吗?
自Spring 3.1起,提供了类似于 @Transactional 注解事务的注解Cache支持,且提供了Cache抽象,在此之前一般通过AOP实现
使用springcache的好处:
- 提供基本的Cache抽象,方便切换各种底层Cache;
- 通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;
- 提供事务回滚时也自动回滚缓存;
- 支持比较复杂的缓存逻辑;
所以说 Spring Cache就是一个缓存框架。它利用了AOP(将缓存逻辑与服务逻辑解耦),实现了基于注解的缓存功能(声明式缓存),并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,用户可以快速将缓存集成到项目中;
三: springcache注解
3.1:@Cacheable注解
==如果缓存中没有:查询数据库,存储缓存,返回结果,==
==如果缓存中有:直接返回结果==
作用:可以用来进行缓存的写入,将结果存储在缓存中,以便于在后续调用的时候可以直接返回缓存中的值,而不必再执行实际的方法。 最简单的使用方式,注解名称=缓存名称,使用例子如下:
@Service @CacheConfig(cacheNames = "role")//提取缓存的前缀配置 public class RoleServiceImpl implements IRoleService { @Autowired private RoleMapper roleMapper; /** * <p> * 几个属性: * a)cacheNames/value:指定缓存组件的名字 * cacheNames = {"role"}可以使用多个参数,是数组的形式,可以指定多个缓存 * b)key:缓存数据使用的key,可以用他来指定。默认是使用方法参数的值 * 编写SpEl: #id #a0,#po,#argrs[0] "0"代表参数的索引 * #result 方法执行后的返回值 * #root.methodName 方法名 * key = "#root.methodName+'['+#id+']'" * e)condition:指定符合缓存的条件 * condition = "#id>0 and #root.methodName eq 'aaa'" 可以多条件判断 * f)unless: 否定缓存,当unless的条件为true,方法结果不会被缓存,可以获取结果进行判断 * unless = "#result==null",结果为null,就不缓存 */ @Override //@Cacheable(cacheNames = "role", key = "#id",condition = "#id>0",unless = "#result==null") @Cacheable(key = "#id",condition = "#id>0",unless = "#result==null") public Role findById(Integer id) { return roleMapper.selectByPrimaryKey(id); } @Cacheable(key ="#root.method.getName()")//直接引用mehtodname异常 @Override public R findAllRole() { List<Role> roleList = roleMapper.findAll(); return R.ok(roleList); } }
3.2:@CacheEvict注解
@CacheEvict:删除缓存的注解,这对删除旧的数据和无用的数据是非常有用的。这里还多了一个参数(allEntries),设置allEntries=true时,可以对整个条目进行批量删除
/** * .@CacheEvict 缓存清除 * key:指定要清除的数据 */ @Override @CacheEvict(key = "#id") public Integer delete(Integer id) { return roleMapper.deleteByPrimaryKey(id); }
3.3:@CachePut注解
@CachePut:当需要更新缓存而不干扰方法的运行时 ,可以使用该注解。也就是说,始终执行该方法,并将结果放入缓存
本质上说,如果存在对应的缓存,则更新覆盖,不存在则添加;
/** * .@CachePut既调用方法、又更新数据,达到同步更新缓存 * <p> * 运行时机: * 1、先调用目标方法 ★★★ * 2、将目标方法的结果缓存起来★★★ * 条件:存取Id的key要保持一致 * key = "#role.id" 传入员工的Id * key = "#result.id" 使用返回员工的Id * 注意: @Cacheable不能使用#result * 因为 @Cacheable在目标方法执行之前需要得到这个key,所以不能用#result */ @Override @CachePut(key = "#result.id")//更新或者添加缓存---》有则更新,无则添加 public Role update(Role role) { roleMapper.updateByPrimaryKey(role); return role; }
3.4:@Caching注释
在使用缓存的时候,有可能会同时进行更新和删除,会出现同时使用多个注解的情况.而@Caching可以实现,对于复杂的缓存策略,我们可借助SpEL实现;
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
/** * .@Caching 定义复杂缓存规则 */ @Override @Caching( cacheable = { @Cacheable(key = "#role.rolename") }, put = { @CachePut(key = "#role.id"), @CachePut(key = "#role.rolecode") }, evict = { @CacheEvict(key = "8") } ) public R add(Role role) { // role.setId(null); try { roleMapper.insert(role); } catch (Exception e) { return R.error(); } return R.ok(role.getId()); }
3.5:注解小结
对于缓存声明,spring的缓存提供了一组java注解:
- @Cacheable
- 功能:触发缓存写入,如果缓存中没有,查询数据库,存储缓存,返回结果,如果缓存中有,直接返回结果
- 应用:查询数据库方法,且查询的数据时热点数据
- @CacheEvict
- 功能:触发缓存清除
- 应用:删除或修改数据库方法
- @CachePut
- 功能:缓存写入(不会影响到方法的运行)。有则更新,无则添加
- 应用:新增到数据库方法
- @Caching
- 功能:重新组合要应用于方法的多个缓存操作
- 应用:上面的注解的组合使用
- @CacheConfig(cacheNames = "xxx")
- 功能:可以提取公共的缓存key的前缀,一般是业务的前缀
- 应用:作用在类之上