前言:缓存在开发中是一个必不可少的优化点,近期在公司的项目重构中,关于缓存优化了不少点,好比在加载一些数据比较多的场景中,会大量使用缓存机制提升接口响应速度,简介提高用户体验。关于缓存,不少人对它都是既爱又恨,爱它的是:它能大幅提高响应效率,恨的是它若是处理很差,没有用比如如LRU这种策略,没有及时更新数据库的数据就会致使数据产生滞后,进而产生用户的误读,或者疑惑。这是很严重的一个问题,好比我在公司和某家公司(国内的一线旅游开发公司)的对接的时候,线上老是出现咱们推送接口数据可是网站的数据产生滞后的现象,询问对方的技术人员,告诉咱们是缓存的问题,只要删除缓存就没事了,我只能无奈...因此如何处理好缓存,对咱们开发人员来讲是一个很棘手的问题。不过关于这一切,springboot已经提供给咱们很便捷的开发工具!本篇博客就来探索springBoot的缓存注解如何使用!web
本篇博客的目录redis
一:springBoot开启缓存注解spring
二:经常使用缓存注解sql
三:使用实例数据库
四:总结缓存
一:springBoot开启注解springboot
1.1:搭建springBoot环境app
在idea中,搭建一个springboot是很简单easy的。接下来我简单说一下步骤:框架
File->new->projiect->Spring Initializer->next->named->web(选中)->Finish->new Windowless
1.2:开始缓存
@SpringBootApplication @EnableAutoConfiguration @EnableCaching public class SpringbootcacheApplication { public static void main(String[] args) { SpringApplication.run(SpringbootcacheApplication.class, args); } }
主要是@EnableCaching用于开启缓存注解的驱动,不然后面使用的缓存都是无效的!
二:经常使用缓存注解
2.1:@CacheConfig
这个注解的的主要做用就是全局配置缓存,好比配置缓存的名字(cacheNames),只须要在类上配置一次,下面的方法就默认以全局配置为主,不须要二次配置,节省了部分代码。
2.2:@Cacheable
这个注解是最重要的,主要实现的功能再进行一个读操做的时候。就是先从缓存中查询,若是查找不到,就会走数据库的执行方法,这是缓存的注解最重要的一个方法,基本上咱们的全部缓存实现都要依赖于它。它具备的属性为cacheNames:缓存名字,condtion:缓存的条件,unless:不缓存的条件。能够指定SPEL表达式来实现,也能够指定缓存的key,缓存的内部实现通常都是key,value形式,相似于一个Map(实际上cacheable的缓存的底层实现就是concurrenHashMap),指定了key,那么缓存就会以key做为键,以方法的返回结果做为值进行映射。
2.3:@CacheEvict
这个注解主要是配合@Cacheable一块儿使用的,它的主要做用就是清除缓存,当方法进行一些更新、删除操做的时候,这个时候就要删除缓存。若是不删除缓存,就会出现读取不到最新缓存的状况,拿到的数据都是过时的。它能够指定缓存的key和conditon,它有一个重要的属性叫作allEntries默认是false,也能够指定为true,主要做用就是清除全部的缓存,而不以指定的key为主。
2.3:@CachePut
这个注解它老是会把数据缓存,而不会去每次作检查它是否存在,相比之下它的使用场景就比较少,毕竟咱们但愿并非每次都把全部的数据都给查出来,咱们仍是但愿能找到缓存的数据,直接返回,这样能提高咱们的软件效率。
2.4:@cache
这个注解它是上面的注解的综合体,包含上面的三个注解(cacheable、cachePut、CacheEvict),可使用这一个注解来包含上面的全部的注解,看源码以下
上面的注解总结以下表格:
三:使用实例
3.1:创建数据库
咱们来新建一个表,含义为文章,下面的示例将会在这张表中进行操做,所使用的框架为SSM+springboot
CREATE TABLE Artile ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` varchar(30) CHARACTER SET gbk COLLATE gbk_chinese_ci NULL DEFAULT NULL , `author` varchar(30) CHARACTER SET gbk COLLATE gbk_chinese_ci NULL DEFAULT NULL , `content` mediumtext CHARACTER SET gbk COLLATE gbk_chinese_ci NULL , `file_name` varchar(30) CHARACTER SET gbk COLLATE gbk_chinese_ci NULL DEFAULT NULL , `state` smallint(2) NULL DEFAULT 1 COMMENT '状态' , PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=gbk COLLATE=gbk_chinese_ci AUTO_INCREMENT=11 ROW_FORMAT=COMPACT ;
3.2:Mapper层
主要就是对Article进行增删改查的业务操做,映射到具体的xml的sql里,而后用service去调用
public interface ArticleMapper { /** * 插入一篇文章 * @param title * @param author * @param content * @param fileName * @return */ public Integer addArticle(@Param("title") String title,@Param("author")String author, @Param("content")String content,@Param("fileName")String fileName); /** * 根据id获取文章 * @param id * @return */ public Article getArticleById(@Param("id") Integer id); /** * 更新content * @param content */ public Integer updateContentById(@Param("content")String content,@Param("id")Integer id); /** * 根据id删除文章 * @param id * @return */ public Integer removeArticleById(@Param("id")Integer id); /** * 得到上一次插入的id * @return */ public Integer getLastInertId(); }
3.3:service层
主要须要注意的是咱们上述讲述的缓存注解都是基于service层(不能放在contoller和dao层),首先咱们在类上配置一个CacheConfig,而后配置一个cacheNames,那么下面的方法都是以这个缓存名字做为默认值,他们的缓存名字都是这个,没必要进行额外的配置。当进行select查询方法的时候,咱们配置上@Cacheable,并指定key,这样除了第一次以外,咱们都会把结果缓存起来,之后的结果都会把这个缓存直接返回。而当进行更新数据(删除或者更新操做)的时候,使用@CacheEvict来清除缓存,防止调用@Cacheabel的时候没有更新缓存
@Service @CacheConfig(cacheNames = "articleCache") public class ArticleService { private AtomicInteger count =new AtomicInteger(0); @Autowired private ArticleMapper articleMapper; /** * 增长一篇文章 每次就进行缓存 * @return */ @CachePut public Integer addArticle(Article article){ Integer result = articleMapper.addArticle(article.getTitle(), article.getAuthor(), article.getContent(), article.getFileName()); if (result>0) { Integer lastInertId = articleMapper.getLastInertId(); System.out.println("--执行增长操做--id:" + lastInertId); } return result; } /** * 获取文章 以传入的id为键,当state为0的时候不进行缓存 * @param id 文章id * @return */ @Cacheable(key = "#id",unless = "#result.state==0") public Article getArticle(Integer id) { try { //模拟耗时操做 Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } final Article artcile = articleMapper.getArticleById(id); System.out.println("--执行数据库查询操做"+count.incrementAndGet()+"次"+"id:"+id); return artcile; } /** * 经过id更新内容 清除以id做为键的缓存 * * @param id * @return */ @CacheEvict(key = "#id") public Integer updateContentById(String contetnt, Integer id) { Integer result = articleMapper.updateContentById(contetnt, id); System.out.println("--执行更新操做id:--"+id); return result; } /** * 经过id移除文章 * @param id 清除以id做为键的缓存 * @return */ @CacheEvict(key = "#id") public Integer removeArticleById(Integer id){ final Integer result = articleMapper.removeArticleById(id); System.out.println("执行删除操做,id:"+id); return result; } }
3.4:controller层
主要是接受客户端的请求,咱们配置了@RestController表示它是一个rest风格的应用程序,在收到add请求会增长一条数据,get请求会查询一条数据,resh会更新一条数据,rem会删除一条数据
@RestController @ComponentScan(basePackages = {"com.wyq.controller", "com.wyq.service"}) @MapperScan(basePackages = {"com.wyq.dao"}) public class ArticleController { @Autowired private ArticleService articleService; @Autowired ArticleMapper articleMapper; @PostMapping("/add") public ResultVo addArticle(@RequestBody Article article) { System.out.println(article.toString()); Integer result = articleService.addArticle(article); if (result >= 0) { return ResultVo.success(result); } return ResultVo.fail(); } @GetMapping("/get") public ResultVo getArticle(@RequestParam("id") Integer id) { Long start = System.currentTimeMillis(); Article article = articleService.getArticle(id); Long end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start)); if (null != article) return ResultVo.success(article); return ResultVo.fail(); } /** * 更新一篇文章 * * @param contetnt * @param id * @return */ @GetMapping("/resh") public ResultVo update(@RequestParam("content") String contetnt, @RequestParam("id") Integer id) { final Integer result = articleService.updateContentById(contetnt, id); if (result > 0) { return ResultVo.success(result); } else { return ResultVo.fail(); } } /** * 删除一篇文章 * * @param id * @return */ @GetMapping("/rem") public ResultVo remove(@RequestParam("id") Integer id) { final Integer result = articleService.removeArticleById(id); if (result > 0) { return ResultVo.success(result); } else { return ResultVo.fail(); } } }
3.5:测试
这里使用postman模拟接口请求
3.5.1:首先咱们来增长一篇文章:请求add接口:
后台返回表示成功:
我看到后台数据库已经插入了数据,它的id是11
3.5.2:执行查询操做
在查询操做中,getArticle,我使用线程睡眠的方式,模拟了5秒的时间来处理耗时性业务,第一次请求确定会查询数据库,理论上第二次请求,将会走缓存,咱们来测试一下:首先执行查询操做
接口响应成功,再看一下后台打印:表示执行了一次查询操做,耗时5078秒
好,重点来了,咱们再次请求接口看看会返回什么?理论上,将不会走数据库执行操做,而且耗时会大大减小:与上面的比对,此次没有打印执行数据库查询操做,证实没有走数据库,而且耗时只有5ms,成功了!缓存发挥做用,从5078秒减少到5秒!大大提高了响应速度,哈哈!
3.5.3:更新操做
当咱们进行修改操做的时候,咱们但愿缓存的数据被清空:看接口返回值成功了,再看数据库
后台控制台打印:
--执行更新操做id:--11
趁热打铁,咱们再次请求三次查询接口,看看会返回什么?每次都会返回这样的结果,可是个人直观感觉就是第一次最慢,第二次、第三次返回都很快
再看看后台打印了什么?执行id为11的数据库查询操做,这是由于缓存被清空了,因此它又走数据库了(得到最新数据),而后后面的查询都会走缓存!很明显,实验成功!
3.5.4:删除操做
同理,在删除操做中,执行了一次删除,那么缓存也会被清空,查询的时候会再次走数据库,这里就不给具体实验效果了,若是须要的同窗,能够把代码下载下来,本身测试一下就知道了。
四:总结
本篇博客介绍了springBoot中缓存的一些使用方法,如何在开发中使用缓存?怎样合理的使用都是值得咱们学习的地方,缓存能大大提高程序的响应速度,提高用户体验,不过它适用的场景也是读多写少的业务场景,若是数据频繁修改,缓存将会失去意义,每次仍是执行的数据库操做!如何使用好它,还有更高效的方式,好比使用redis\memoryCache等专业组件,本篇博客只是探讨的spring的注解缓存,相对来讲比较简单。但愿起到抛砖引玉的做用,在之后博客中,我将介绍redis如何搭建集群来实现缓存!
本篇博客的代码示例下载地址(适用于intel idea):连接:https://pan.baidu.com/s/1CkRCFTlzfbKyg1R15Er6tw 密码:nda4(若是文件失效请及时联系我,补链)