1.情景展示
在实际开发过程中,我们为了减少对数据库的频繁访问,会把不易更改的数据放到缓存中,减少对数据库的访问,以此,既能减少数据库的操作次数,也能节省响应时间;
但是,缓存同样是一把双刃剑,也会给我们带来不便,比如:
对于后端开发人员来说,我们习惯于直接操作数据库完成对数据库的修改,而不是通过前端发起请求,这样,就会导致一个问题:
不会触发缓存更新操作,数据库虽然已经改好,但是,缓存没有得到更新;
或者是,有的项目根本就不涉及修改操作,只负责读取数据,这就不存在更新缓存的情形;
而一旦数据库发生修改,就无法保持数据同步,这就非常尴尬了,怎么办?
2.情况分析
我们通常使用的缓存注解是@CacheEnable,该注解只在第一次请求时会从数据库拿到数据,并放到缓存当中,后续请求将直接从缓存读取;
鉴于spring启用缓存,是通过使用注解@EnableCaching来完成的,我脑海里首先想到的是:
能不能在配置文件中进行动态设置,换言之就是:将是否启动缓存的决定权迁移到配置文件中,比如:application.yml中。
事实证明,我想太多了,无法实现!
3.解决方案
我们知道,spring的缓存只存在于项目运行期间,它不像redis,即使你把项目停了,缓存依然会存在;
由此,就诞生了第一种解决方案:
方案一:重启项目
重启项目,所有的缓存将不复存在。
方案二:禁用缓存
我们只要把使用注解@EnableCaching删掉或者是注销掉,重启项目就OK啦。
但是,这对于已经部署在Linux服务器下的项目而言,可操作性不强,我们还得替换class文件才行。
方案三:手动触发清除缓存操作
可能,会有小伙伴说,设置缓存的失效时间不就完了,可以设置失效时间,但那是只有到指定时间后才会失效,不能满足我们的及时性,总不能一直等到缓存失效,再干活吧?
我们可以创建控制器,向外提供接口,手动清除缓存,通过这种方式来间接实现缓存的清除操作,这样,也不用重启服务器,缓存也能接着用(重启服务器的目的就是为了清除缓存)
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiSupport; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import javax.annotation.Resource; import javax.validation.constraints.NotBlank;
/** * 清除缓存控制器 * @description: * @author: Marydon * @date: 2020-12-16 12:51 * @version: 1.0 * @email: marydon20170307@163.com */ @Api("CacheController") @ApiSupport(order = 266, author = "Marydon") @Slf4j // 必须作用在类上,不然只能校验Null,不能校验Empty @Validated @Controller public class CacheController { // 注入缓存管理器 @Resource private CacheManager cacheManager; @ApiOperation(value = "清除所有缓存", notes = "清空所有缓存") @ApiOperationSupport(author = "Marydon", order = 1) @GetMapping("/clearAll") public String clearAllCache() { cacheManager.getCacheNames().forEach(cacheName -> { cacheManager.getCache(cacheName).clear(); log.info("缓存".concat(cacheName).concat("清理成功!")); }); return "所有缓存清理完毕"; } @ApiOperation(value = "清除指定缓存", notes = "根据入参清除缓存") @ApiOperationSupport(author = "Marydon", order = 10) // knife4j会将headers的值识别为响应数据类型 @RequestMapping(value = "clear", method = {RequestMethod.GET, RequestMethod.POST}, headers = {"Accept=text/pain"}) // 入参需要使用@RequestParam,否则请求会被spring拦截,进不来(请求数据类型为空) public String clearCacheByName(@ApiParam(value = "缓存名称", required = true, example = "aaCache") @RequestParam @NotBlank(message = "缓存名称不能为空") String cacheName) { Cache cache = cacheManager.getCache(cacheName); if (null == cache) return cacheName + "并不存在"; cache.clear(); log.info(cacheName + "缓存清理完毕"); return cacheName + "缓存清理完毕"; } }
上面提供了两种清除缓存的方法,一种是清除所有缓存,另一种是清除指定缓存。
4.测试
调用使用缓存的地方,一般是从前端发起请求;
初次请求,从数据库拿数据;
清空控制台,现在来二次请求;
已经不再从数据库获取数据
清空控制台,仙子,我们来调用清空缓存操作:
缓存清理成功
此时,我们发起第三次触发缓存的请求
从数据库读取,这就证明了咱们的代码生效了,搞定。
5.bug修复
2020-12-17
上面的代码存在一个问题:我们实际想返回字符串信息,但是实际上,springmvc会自动将字符串识别为路径,这就非常尴尬,返回的就是404;
如何让springmvc不把它当成路径,而是作为数据返回呢?
这里提供三种实现方式:
最古老的方式:返回字符流
第二种:将@Controller替换成@RestController
第三种:在接口方法上添加@ResponseBody