springboot缓存开发实战

前言:缓存在开发中是一个必不可少的优化点,近期在公司的项目重构中,关于缓存优化了很多点,比如在加载一些数据比较多的场景中,会大量使用缓存机制提高接口响应速度,简介提升用户体验。关于缓存,很多人对它都是既爱又恨,爱它的是:它能大幅提升响应效率,恨的是它如果处理不好,没有用好比如LRU这种策略,没有及时更新数据库的数据就会导致数据产生滞后,进而产生用户的误读,或者疑惑。这是很严重的一个问题,比如我在公司和某家公司(国内的一线旅游开发公司)的对接的时候,线上总是出现我们推送接口数据但是网站的数据产生滞后的现象,询问对方的技术人员,告诉我们是缓存的问题,只要删除缓存就没事了,我只能无奈…所以如何处理好缓存,对我们开发人员来说是一个很棘手的问题。不过关于这一切,springboot已经提供给我们很便捷的开发工具!本篇博客就来探索springBoot的缓存注解如何使用!

本篇博客的目录

一:springBoot开启缓存注解

二:常用缓存注解

三:使用实例

四:总结

一:springBoot开启注解

1.1:搭建springBoot环境

在idea中,搭建一个springboot是很简单easy的。接下来我简单说一下步骤:

File->new->projiect->Spring Initializer->next->named->web(选中)->Finish->new Window

1.2:开始缓存

@SpringBootApplication

@EnableAutoConfiguration

@EnableCaching

publicclassSpringbootcacheApplication{

publicstaticvoidmain(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

CREATETABLEArtile (

`id`int(11)NOTNULLAUTO_INCREMENT ,

`title`varchar(30)CHARACTERSETgbkCOLLATEgbk_chinese_ciNULLDEFAULTNULL,

`author`varchar(30)CHARACTERSETgbkCOLLATEgbk_chinese_ciNULLDEFAULTNULL,

`content`mediumtextCHARACTERSETgbkCOLLATEgbk_chinese_ciNULL,

`file_name`varchar(30)CHARACTERSETgbkCOLLATEgbk_chinese_ciNULLDEFAULTNULL,

`state`smallint(2)NULLDEFAULT1COMMENT'状态',

PRIMARYKEY(`id`)

)

ENGINE=InnoDB

DEFAULTCHARACTERSET=gbkCOLLATE=gbk_chinese_ci

AUTO_INCREMENT=11

ROW_FORMAT=COMPACT

;

3.2:Mapper层

主要就是对Article进行增删改查的业务操作,映射到具体的xml的sql里,然后用service去调用

publicinterfaceArticleMapper{

/**

* 插入一篇文章

*@paramtitle

*@paramauthor

*@paramcontent

*@paramfileName

*@return

*/

publicInteger addArticle(@Param("title")String  title,@Param("author")String author,

@Param("content")String content,@Param("fileName")String fileName);

/**

* 根据id获取文章

*@paramid

*@return

*/

publicArticle getArticleById(@Param("id")Integer id);

/**

* 更新content

*@paramcontent

*/

publicInteger updateContentById(@Param("content")String content,@Param("id")Integer id);

/**

* 根据id删除文章

*@paramid

*@return

*/

publicInteger removeArticleById(@Param("id")Integer id);

/**

* 获得上一次插入的id

*@return

*/

publicInteger getLastInertId();

}

3.3:service层

主要需要注意的是我们上述讲述的缓存注解都是基于service层(不能放在contoller和dao层),首先我们在类上配置一个CacheConfig,然后配置一个cacheNames,那么下面的方法都是以这个缓存名字作为默认值,他们的缓存名字都是这个,不必进行额外的配置。当进行select查询方法的时候,我们配置上@Cacheable,并指定key,这样除了第一次之外,我们都会把结果缓存起来,以后的结果都会把这个缓存直接返回。而当进行更新数据(删除或者更新操作)的时候,使用@CacheEvict来清除缓存,防止调用@Cacheabel的时候没有更新缓存

@Service

@CacheConfig(cacheNames ="articleCache")

publicclassArticleService{

privateAtomicInteger count =new AtomicInteger(0);

@Autowired

privateArticleMapper articleMapper;

/**

* 增加一篇文章 每次就进行缓存

*@return

*/

@CachePut

publicInteger 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);

}

returnresult;

}

/**

* 获取文章  以传入的id为键,当state为0的时候不进行缓存

*@paramid 文章id

*@return

*/

@Cacheable(key ="#id",unless ="#result.state==0")

publicArticle getArticle(Integer id) {

try{

//模拟耗时操作

Thread.sleep(5000);

}catch(InterruptedException e) {

e.printStackTrace();

}

finalArticle artcile = articleMapper.getArticleById(id);

System.out.println("--执行数据库查询操作"+count.incrementAndGet()+"次"+"id:"+id);

returnartcile;

}

/**

* 通过id更新内容 清除以id作为键的缓存

*

*@paramid

*@return

*/

@CacheEvict(key ="#id")

publicInteger updateContentById(String contetnt, Integer id) {

Integer result = articleMapper.updateContentById(contetnt, id);

System.out.println("--执行更新操作id:--"+id);

returnresult;

}

/**

* 通过id移除文章

*@paramid  清除以id作为键的缓存

*@return

*/

@CacheEvict(key ="#id")

publicInteger removeArticleById(Integer id){

finalInteger result = articleMapper.removeArticleById(id);

System.out.println("执行删除操作,id:"+id);

returnresult;

}

}

3.4:controller层

主要是接受客户端的请求,我们配置了@RestController表示它是一个rest风格的应用程序,在收到add请求会增加一条数据,get请求会查询一条数据,resh会更新一条数据,rem会删除一条数据

@RestController

@ComponentScan(basePackages = {"com.wyq.controller","com.wyq.service"})

@MapperScan(basePackages = {"com.wyq.dao"})

publicclassArticleController{

@Autowired

privateArticleService articleService;

@Autowired

ArticleMapper articleMapper;

@PostMapping("/add")

publicResultVo addArticle(@RequestBodyArticle article) {

System.out.println(article.toString());

Integer result = articleService.addArticle(article);

if(result >=0) {

returnResultVo.success(result);

}

returnResultVo.fail();

}

@GetMapping("/get")

publicResultVo getArticle(@RequestParam("id")Integer id) {

Longstart = System.currentTimeMillis();

Article article = articleService.getArticle(id);

Longend = System.currentTimeMillis();

System.out.println("耗时:"+(end-start));

if(null!= article)

returnResultVo.success(article);

returnResultVo.fail();

}

/**

* 更新一篇文章

*

*@paramcontetnt

*@paramid

*@return

*/

@GetMapping("/resh")

publicResultVo update(@RequestParam("content")String contetnt,@RequestParam("id")Integer id) {

finalInteger result = articleService.updateContentById(contetnt, id);

if(result >0) {

returnResultVo.success(result);

}else{

returnResultVo.fail();

}

}

/**

* 删除一篇文章

*

*@paramid

*@return

*/

@GetMapping("/rem")

publicResultVo remove(@RequestParam("id")Integer id) {

finalInteger result = articleService.removeArticleById(id);

if(result >0) {

returnResultVo.success(result);

}else{

returnResultVo.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如何搭建集群来实现缓存!

扩展阅读

缓存在高并发场景下的常见问题

面试常考!缓存三大问题及解决方案

Java Web现代化开发:Spring Boot + Mybatis + Redis二级缓存

来源:https://www.cnblogs.com/wyq178/p/9840985.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • ©著作权归作者所有:来自51CTO博客作者优秀android的原创作品,如需转载,请注明出处,否则将追究法律责任 ...
    传奇内服号阅读 1,069评论 0 9
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,920评论 2 89
  • 写在前面 很早之前,知道有本书叫做《高效能人士的七个习惯》。 很久之后,依然未读,从名字就嗅得出浓浓的鸡汤味。 不...
    常明的学习探索阅读 512评论 0 2
  • 《永远作为第一次》 (法国)安德烈·布勒东(André Breton) 永远作为第一次 ...
    北望说阅读 640评论 12 8