您的位置:首页 > 数据库 > Redis

Spring Boot 整合 Redis 实现缓存操作

2017-04-15 12:05 1106 查看
摘要:原创出处www.bysocket.com「泥瓦匠BYSocket」欢迎转载,保留摘要,谢谢!

『产品没有价值,开发团队再优秀也无济于事–《启示录》』

本文提纲

一、缓存的应用场景

二、更新缓存的策略

三、运行springboot-mybatis-redis工程案例

四、springboot-mybatis-redis工程代码配置详解

运行环境

MacOS10.12.x

JDK8+

Redis3.2.8

SpringBoot1.5.1.RELEASE

强烈推荐读者-

「springboot-learning-example」开源项目,Star一下

springboot实践学习案例,是springboot初学者及核心技术巩固的最佳实践。
https://git.oschina.net/jeff1993/springboot-learning-example

一、缓存的应用场景

什么是缓存?

在互联网场景下,尤其2C端大流量场景下,需要将一些经常展现和不会频繁变更的数据,存放在存取速率更快的地方。缓存就是一个存储器,在技术选型中,常用Redis作为缓存数据库。缓存主要是在获取资源方便性能优化的关键方面。

Redis是一个高性能的key-value数据库。GitHub地址:https://github.com/antirez/redis。Github是这么描述的:

Redisisanin-memorydatabasethatpersistsondisk.Thedatamodeliskey-value,butmanydifferentkindofvaluesaresupported:Strings,Lists,Sets,SortedSets,Hashes,HyperLogLogs,Bitmaps.

缓存的应用场景有哪些呢?

比如常见的电商场景,根据商品ID获取商品信息时,店铺信息和商品详情信息就可以缓存在Redis,直接从Redis获取。减少了去数据库查询的次数。但会出现新的问题,就是如何对缓存进行更新?这就是下面要讲的。

二、更新缓存的策略

参考《缓存更新的套路》/detail/2680607929.html,缓存更新的模式有四种:Cacheaside,Readthrough,Writethrough,Writebehindcaching。

这里我们使用的是CacheAside策略,从三个维度:(摘自耗子叔叔博客)

失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

命中:应用程序从cache中取数据,取到后返回。

更新:先把数据存到数据库中,成功后,再让缓存失效。

大致流程如下:

获取商品详情举例

a.从商品Cache中获取商品详情,如果存在,则返回获取Cache数据返回。

b.如果不存在,则从商品DB中获取。获取成功后,将数据存到Cache中。则下次获取商品详情,就可以从Cache就可以得到商品详情数据。

c.从商品DB中更新或者删除商品详情成功后,则从缓存中删除对应商品的详情缓存





三、运行springboot-mybatis-redis工程案例

gitclone下载工程springboot-learning-example,项目地址见GitHub–https://github.com/JeffLi1993/springboot-learning-example。

下面开始运行工程步骤(QuickStart):

1.数据库和Redis准备

a.创建数据库springbootdb:

1
CREATEDATABASEspringbootdb;
b.创建表city:(因为我喜欢徒步)

1
2
3
4
5
6
7
8
DROPTABLEIFEXISTS`city`;
CREATETABLE`city`(
`id`int(10)unsignedNOTNULLAUTO_INCREMENTCOMMENT'城市编号',
`province_id`int(10)unsignedNOTNULLCOMMENT'省份编号',
`city_name`varchar(25)DEFAULTNULLCOMMENT'城市名称',
`description`varchar(25)DEFAULTNULLCOMMENT'描述',
PRIMARYKEY(`id`)
)ENGINE=InnoDBAUTO_INCREMENT=1DEFAULTCHARSET=utf8;
c.插入数据

1
INSERTcityVALUES(1,1,'温岭市','BYSocket的家在温岭。');
d.本地安装Redis

详见写过的文章《Redis安装》http://www.bysocket.com/?p=917

2.springboot-mybatis-redis工程项目结构介绍

1
2
3
4
5
6
7
springboot-mybatis-redis工程项目结构如下图所示:
org.spring.springboot.controller-Controller层
org.spring.springboot.dao-数据操作层DAO
org.spring.springboot.domain-实体类
org.spring.springboot.service-业务逻辑层
Application-应用启动类
application.properties-应用配置文件,应用启动会自动读取配置
3.改数据库配置

打开application.properties文件,修改相应的数据源配置,比如数据源地址、账号、密码等。

(如果不是用MySQL,自行添加连接驱动pom,然后修改驱动名配置。)

4.编译工程

在项目根目录springboot-learning-example,运行maven指令:

1
mvncleaninstall
5.运行工程

右键运行springboot-mybatis-redis工程Application应用启动类的main函数。

项目运行成功后,这是个HTTPOVERJSON服务项目。所以用postman工具可以如下操作

根据ID,获取城市信息

GEThttp://127.0.0.1:8080/api/city/1
再请求一次,获取城市信息会发现数据获取的耗时快了很多。服务端Console输出的日志:

1
2
2017-04-1318:29:00.273INFO13038---[nio-8080-exec-1]o.s.s.service.impl.CityServiceImpl:CityServiceImpl.findCityById():城市插入缓存>>City{id=12,provinceId=3,cityName='三亚',description='水好,天蓝'}
2017-04-1318:29:03.145INFO13038---[nio-8080-exec-2]o.s.s.service.impl.CityServiceImpl:CityServiceImpl.findCityById():从缓存中获取了城市>>City{id=12,provinceId=3,cityName='三亚',description='水好,天蓝'}
可见,第一次是从数据库DB获取数据,并插入缓存,第二次直接从缓存中取。

更新城市信息

PUThttp://127.0.0.1:8080/api/city
删除城市信息

DELETEhttp://127.0.0.1:8080/api/city/2
这两种操作中,如果缓存有对应的数据,则删除缓存。服务端Console输出的日志:

1
2017-04-1318:29:52.248INFO13038---[nio-8080-exec-9]o.s.s.service.impl.CityServiceImpl:CityServiceImpl.deleteCity():从缓存中删除城市ID>>12

四、springboot-mybatis-redis工程代码配置详解

这里,我强烈推荐注解的方式实现对象的缓存。但是这里为了更好说明缓存更新策略。下面讲讲工程代码的实现。

pom.xml依赖配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?xmlversion="1.0"encoding="UTF-8"?>
<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0>'target='_blank'>http://maven.apache.org/xsd/maven-4.0.0.xsd"[/code]><modelVersion>4.0.0</modelVersion>

<groupId>springboot</groupId>
<artifactId>springboot-mybatis-redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-mybatis-redis::整合Mybatis并使用Redis作为缓存</name>

<!--SpringBoot启动父依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>

<properties>
<mybatis-spring-boot>1.2.0</mybatis-spring-boot>
<mysql-connector>5.1.39</mysql-connector>
<spring-boot-starter-redis-version>1.3.2.RELEASE</spring-boot-starter-redis-version>
</properties>

<dependencies>

<!--SpringBootReids依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>${spring-boot-starter-redis-version}</version>
</dependency>

<!--SpringBootWeb依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--SpringBootTest依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--SpringBootMybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot}</version>
</dependency>

<!--MySQL连接驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector}</version>
</dependency>

<!--Junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
包括了SpringBootReids依赖、MySQL依赖和Mybatis依赖。

在application.properties应用配置文件,增加Redis相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
##数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdb?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

##Mybatis配置
mybatis.typeAliasesPackage=org.spring.springboot.domain
mybatis.mapperLocations=classpath:mapper/*.xml

##Redis配置
##Redis数据库索引(默认为0)
spring.redis.database=0
##Redis服务器地址
spring.redis.host=127.0.0.1
##Redis服务器连接端口
spring.redis.port=6379
##Redis服务器连接密码(默认为空)
spring.redis.password=
##连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
##连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
##连接池中的最大空闲连接
spring.redis.pool.max-idle=8
##连接池中的最小空闲连接
spring.redis.pool.min-idle=0
##连接超时时间(毫秒)
spring.redis.timeout=0
详细解释可以参考注释。对应的配置类:org.springframework.boot.autoconfigure.data.redis.RedisProperties

CityRestController控制层依旧是Restful风格的,详情可以参考《Springboot实现Restful服务,基于HTTP/JSON传输》。http://www.bysocket.com/?p=1627domain对象City必须实现序列化,因为需要将对象序列化后存储到Redis。如果没实现Serializable,控制台会爆出以下异常:

1
2
Serializable
java.lang.IllegalArgumentException:DefaultSerializerrequiresaSerializablepayloadbutreceivedanobjectoftype
City.java城市对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
packageorg.spring.springboot.domain;

importjava.io.Serializable;

/**
*城市实体类
*
*Createdbybysocketon07/02/2017.
*/
publicclassCityimplementsSerializable{

privatestaticfinallongserialVersionUID=-1L;

/**
*城市编号
*/
privateLongid;

/**
*省份编号
*/
privateLongprovinceId;

/**
*城市名称
*/
privateStringcityName;

/**
*描述
*/
privateStringdescription;

publicLonggetId(){
returnid;
}

publicvoidsetId(Longid){
this.id=id;
}

publicLonggetProvinceId(){
returnprovinceId;
}

publicvoidsetProvinceId(LongprovinceId){
this.provinceId=provinceId;
}

publicStringgetCityName(){
returncityName;
}

publicvoidsetCityName(StringcityName){
this.cityName=cityName;
}

publicStringgetDescription(){
returndescription;
}

publicvoidsetDescription(Stringdescription){
this.description=description;
}

@Override
publicStringtoString(){
return"City{"+
"id="+id+
",provinceId="+provinceId+
",cityName='"+cityName+'\''+
",description='"+description+'\''+
'}';
}
}
如果需要自定义序列化实现,只要实现RedisSerializer接口去实现即可,然后在使用RedisTemplate.setValueSerializer方法去设置你实现的序列化实现。

主要还是城市业务逻辑实现类CityServiceImpl.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
packageorg.spring.springboot.service.impl;

importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.spring.springboot.dao.CityDao;
importorg.spring.springboot.domain.City;
importorg.spring.springboot.service.CityService;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.data.redis.core.RedisTemplate;
importorg.springframework.data.redis.core.StringRedisTemplate;
importorg.springframework.data.redis.core.ValueOperations;
importorg.springframework.stereotype.Service;

importjava.util.List;
importjava.util.concurrent.TimeUnit;

/**
*城市业务逻辑实现类
*<p>
*Createdbybysocketon07/02/2017.
*/
@Service
publicclassCityServiceImplimplementsCityService{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(CityServiceImpl.class);

@Autowired
privateCityDaocityDao;

@Autowired
privateRedisTemplateredisTemplate;

/**
*获取城市逻辑:
*如果缓存存在,从缓存中获取城市信息
*如果缓存不存在,从DB中获取城市信息,然后插入缓存
*/
publicCityfindCityById(Longid){
//从缓存中获取城市信息
Stringkey="city_"+id;
ValueOperations<String,City>operations=redisTemplate.opsForValue();

//缓存存在
booleanhasKey=redisTemplate.hasKey(key);
if(hasKey){
Citycity=operations.get(key);

LOGGER.info("CityServiceImpl.findCityById():从缓存中获取了城市>>"+city.toString());
returncity;
}

//从DB中获取城市信息
Citycity=cityDao.findById(id);

//插入缓存
operations.set(key,city,10,TimeUnit.SECONDS);
LOGGER.info("CityServiceImpl.findCityById():城市插入缓存>>"+city.toString());

returncity;
}

@Override
publicLongsaveCity(Citycity){
returncityDao.saveCity(city);
}

/**
*更新城市逻辑:
*如果缓存存在,删除
*如果缓存不存在,不操作
*/
@Override
publicLongupdateCity(Citycity){
Longret=cityDao.updateCity(city);

//缓存存在,删除缓存
Stringkey="city_"+city.getId();
booleanhasKey=redisTemplate.hasKey(key);
if(hasKey){
redisTemplate.delete(key);

LOGGER.info("CityServiceImpl.updateCity():从缓存中删除城市>>"+city.toString());
}

returnret;
}

@Override
publicLongdeleteCity(Longid){

Longret=cityDao.deleteCity(id);

//缓存存在,删除缓存
Stringkey="city_"+id;
booleanhasKey=redisTemplate.hasKey(key);
if(hasKey){
redisTemplate.delete(key);

LOGGER.info("CityServiceImpl.deleteCity():从缓存中删除城市ID>>"+id);
}
returnret;
}

}
首先这里注入了RedisTemplate对象。联想到Spring的JdbcTemplate,RedisTemplate封装了RedisConnection,具有连接管理,序列化和Redis操作等功能。还有针对String的支持对象StringRedisTemplate。

Redis操作视图接口类用的是ValueOperations,对应的是RedisString/Value操作。还有其他的操作视图,ListOperations、SetOperations、ZSetOperations和HashOperations。ValueOperations插入缓存是可以设置失效时间,这里设置的失效时间是10s。

回到更新缓存的逻辑

a.findCityById获取城市逻辑:

如果缓存存在,从缓存中获取城市信息

如果缓存不存在,从DB中获取城市信息,然后插入缓存

b.deleteCity删除/updateCity更新城市逻辑:

如果缓存存在,删除

如果缓存不存在,不操作

其他不明白的,可以gitclone下载工程springboot-learning-example,工程代码注解很详细。https://github.com/JeffLi1993/springboot-learning-example。

五、小结

本文涉及到SpringBoot在使用Redis缓存时,一个是缓存对象需要序列化,二个是缓存更新策略是如何的。

欢迎扫一扫我的公众号关注—及时得到博客订阅哦!

http://www.bysocket.com/—

https://github.com/JeffLi1993—



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: