SpringBoot整合Redis
本小记学习目标:
1、相关准备知识
2、入门简单项目介绍
3、SpringBoot中配置Re
dis
4、Re
dis的特殊
用法
5、Spring缓存注解操作Re
dis
一、相关准备知识
Window环境中Redis的安装
下载Windows版本的Re
dis,解压
首先在解压后的目录中找到re
dis.window.conf
添加如下两个配置,以免在后续启动re
dis的时候内存分配存在异常
maxheap 180MB
maxmemory 120MB
启动re
dis服务,打开CMD命令窗口,定位到解压目录后,执行如下命令
.\re
dis-server .\re
dis.windows.conf
执行命令后
提示如下则表示服务端启动成功
The server is
Now ready to accept connections on port 6379
打开另
一个cmd窗口模拟客户端访问服务器,运行如命令
re
dis-cli -p 6379 -a xiaoxie
注意:这里的xiaoxie,是配置的认证密码,在re
dis.windows.conf
文件中可做如下配置
# 认证密码
requirepass xiaoxie
当客户端运行上面的命令后有如下
提示则表示连接成功
127.0.0.1:6379>
Redis相关简介
Re
dis是一种运行在内存的
数据库,它
支持7种数据类型的存储【字符串(String)、列表(List)、集合(Set)、Sorted Set(有序集合)、Hash(哈希)、Module(模块)、Streams(流信息)】。
Re
dis有如下几个关键词
开源、ANSI C语言编写、遵守BSD协议、
支持网络、可基于内存、可持久化、日志型、键值
数据库、提供API
一般来说应用的
查询要远远多于更新,
查询与更新的比例大约在9:1到7:3,对于
查询比例多的应用,使用Re
dis可以明显提升
性能。
Re
dis也提供了简单的事务机制来保证在高并发的情况下数据的一致性。
SpringBoot中对于Redis的依赖
<!-- re
dis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>
spring-boot-starter-data-re
dis</artifactId>
<exclusions>
<!-- 不依赖Re
dis的异步客户端lettuce -->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Re
dis客户端驱动je
dis -->
<dependency>
<groupId>re
dis.clients</groupId>
<artifactId>je
dis</artifactId>
</dependency>
注意:对于SpringBoot 2.x版本会引入Lettuce的Re
dis客户端的驱动,但我们一般常用的会是Je
dis,所以可以看到上面的依赖中有<exclusions>元素排除了Lettuce,并且同时引入了je
dis的依赖。
二、入门简单项目介绍
Spring提供了
一个Re
disConnectionFactory接口,通过它可以
生成一个Re
disConnection接口对象,而
生成的这个接口对象则是对Re
dis底层接口的封装。
Spring中通过Re
disConnection接口操作Re
dis,Re
disConnection则对原生的Je
dis进行封装。
要
获取Re
disConnection接口对象,是通过Re
disConnectionFactory接口
生成的,因而第一步要去配置的就是这个工厂,而配置这个工厂则主要是配置Re
dis的连接池,对于连接池可以限定其最大连接数、超时时间等
属性。
创建Re
disConnectionFactory对象,新增类:com.xiaoxie.con
fig.Re
disCon
fig
package com.xiaoxie.con
fig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Con
figuration;
import org.springframework.data.re
dis.connection.Re
disConnectionFactory;
import org.springframework.data.re
dis.connection.Re
disStandaloneCon
figuration;
import org.springframework.data.re
dis.connection.je
dis.Je
disConnectionFactory;
import re
dis.clients.je
dis.Je
disPoolCon
fig;
@Con
figuration
public class Re
disCon
fig {
private Re
disConnectionFactory connectionFactory = null;
@Bean(name = "Re
disConnectionFactory")
public Re
disConnectionFactory initRe
disConnectionFactory(){
if(this.connectionFactory != null){
return this.connectionFactory;
}
Je
disPoolCon
fig poolCon
fig = new Je
disPoolCon
fig();
//最大空闲数
poolCon
fig.setMaxIdle(30);
//最大连接数
poolCon
fig.setMaxTotal(50);
//最大等待毫秒数
poolCon
fig.setMaxWaitMillis(2000);
//创建Je
dis连接工厂
Je
disConnectionFactory connectionFactory = new Je
disConnectionFactory(poolCon
fig);
//
获取Re
dis配置
Re
disStandaloneCon
figuration rsCfg = connectionFactory.getStandaloneCon
figuration();
connectionFactory.setHostName("127.0.0.1");
connectionFactory.setPort(6379);
connectionFactory.setPassword("xiaoxie");
this.connectionFactory = connectionFactory;
return connectionFactory;
}
当我们有了Re
disConnectionFactory,通过它则可以创建Re
disConnection接口对象。但是当我们在使用时要先从Re
disConnectionFactory工厂
获取,然后在使用完后还要自行
关闭它,Spring中为了简化开发的过程,提供了
Redistemplate
Redi
stemplate首先会从Re
disConnectionFactory工厂中
获取连接,然
后执行对应的Re
dis命令,最后还会去
关闭Re
dis的连接。这些
内容都
在这个类中被封装好了。
创建Redi
stemplate,在Re
disCon
fig类中新增
一个Bean
方法
//创建Redi
stemplate
@Bean(name = "redi
stemplate")
public Redi
stemplate<Object,Object> initRedi
stemplate(){
Redi
stemplate<Object,Object> redi
stemplate = new Redi
stemplate<>();
//
在这里获取StringRe
disSerializer
Re
disSerializer stringRe
disSerializer = redi
stemplate.getStringSerializer();
//设置字符串序列化器
redi
stemplate.setKeySerializer(stringRe
disSerializer);
redi
stemplate.setValueSerializer(stringRe
disSerializer);
redi
stemplate.setHashKeySerializer(stringRe
disSerializer);
redi
stemplate.setHashValueSerializer(stringRe
disSerializer);
redi
stemplate.setConnectionFactory(initRe
disConnectionFactory());
return redi
stemplate;
}
这里有
一个需要说明的是Re
disSerializer
Re
dis是一种基于字符串存储的No
sql,然而Java是基于对象的语言,对象是无法直接存储到Re
dis当中的,这时就会用到Java的序列化机制,当
java类实现了java.io.Serializable接口,就代表类的对象是可以进行序列化的,通过把类对象进行序列化就可以得到二进制字符串,这样Re
dis就可以把这些类对象以字符串的进行存储。同样Java也可以把那些二进制字符串通过反序列化转换为对象。根据这个原理,Spring提供了序列化器的机制,并且实现了几个序列化
Spring提供了Re
disSerializer接口,它有如下两个
方法:
serialize:把可以序列化的对象转换为二进制字符串
deserialize:把二进制字符串换为Java对象
它的实现类中有两个重要的类:
StringRe
disSerializer
JdkSerializationRe
disSerializer(
默认)
上面的
代码中先
获取字符串序列化器再把它设置到需要使用字符串序列化的部分。
Spring对Redis数据类型操作的封装
Spring-data-re
dis数据类型封装操作接口对照表
操作接口
|
功能
|
获取方式
|
备注
|
GeoOperations
|
地理位置操作
|
redistemplate.opsForGeo()
|
使用频率低
|
HashOpertions
| 散列操作接口 |
redistemplate.opsForHash()
|
|
HyperLoglogoperations
|
基数操作接口
|
redistemplate.opsForHyperLogLog()
|
使用频率低
|
ListOperations
|
列表(链表)操作接口
|
redistemplate.opsForList()
|
|
Setoperations
|
集合操作接口
|
redistemplate.opsForSet()
|
|
ValueOperations
|
字符串操作接口
|
redistemplate.opsForValue()
|
|
ZSetoperations
|
有序集合操作接口
|
redistemplate.opsForZSet()
|
|
当我们需要对某
一个键值做连续操作时,Spring提供了对应的BoundXXXOperations接口
操作接口
|
获取方式
| 说明 |
BoundGeoOperations
|
redistemplate.boundGeoOps("geo")
|
绑定一个地理位置数据类型的键操作,使用频率低
|
BoundHashOperations
|
redistemplate.boundHashOps("hash")
|
绑定一个散列数据类型键操作
|
BoundListOperations
|
redistemplate.boundListOps("list")
|
绑定一个列表(链表)数据类型键操作
|
BoundSetoperations
|
redistemplate.boundSetops("set")
|
绑定一个集合数据类型键操作
|
BoundValueOperations
|
redistemplate.boundValueOps("string")
|
绑定一个字符串集合数据类型的键操作
|
BoundZSetopertaions
|
redistemplate.boundZSetops("zset")
|
绑定一个有序集合数据类型的键操作
|
在SpringBoot的启动类的main
方法中
添加如下
代码,则可以向re
dis中写入对应的值
package com.xiaoxie;
import com.xiaoxie.con
fig.Re
disCon
fig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autocon
figure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationCon
figApplicationContext;
import org.springframework.data.re
dis.core.Redi
stemplate;
@SpringBootApplication
public class SpringBootStartApplication {
public static void main(String[] args) {
Sy
stem.out.println("Redi
stemplate测试:");
ApplicationContext ctx = new AnnotationCon
figApplicationContext(Re
disCon
fig.class);
Redi
stemplate redi
stemplate = (Redi
stemplate) ctx.getBean("redi
stemplate");
redi
stemplate.opsForValue().set("key1","value1");
redi
stemplate.opsForHash().put("hash","field","hvalue");
Sy
stem.out.println("Redi
stemplate测试完成,写入相应的值");
SpringApplication.run(SpringBootStartApplication.class,args);
}
}
SessionCallback和RedisCallback接口
这两个接口的作用是让Redi
stemplate进行回调,通过它们可以在同一条连接下执行多个Re
dis命令。
这两个接口SessionCallback提供的封装良好,使用率更高。
修改main
方法中执行Re
dis命名的过程,使用Re
disCallback接口和SessionCallback来同时执行两个写入值的命令
package com.xiaoxie;
import com.xiaoxie.con
fig.Re
disCon
fig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autocon
figure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationCon
figApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.data.re
dis.connection.Re
disConnection;
import org.springframework.data.re
dis.core.Re
disCallback;
import org.springframework.data.re
dis.core.Re
disOperations;
import org.springframework.data.re
dis.core.Redi
stemplate;
import org.springframework.data.re
dis.core.SessionCallback;
@SpringBootApplication
public class SpringBootStartApplication {
public static void main(String[] args) {
Sy
stem.out.println("Redi
stemplate测试:");
ApplicationContext ctx = new AnnotationCon
figApplicationContext(Re
disCon
fig.class);
Redi
stemplate redi
stemplate = (Redi
stemplate) ctx.getBean("redi
stemplate");
/*redi
stemplate.opsForValue().set("key1","value1");
redi
stemplate.opsForHash().put("hash","field","hvalue");*/
//如下使用Re
disCallback进行回调
/*redi
stemplate.execute(new Re
disCallback() {
@Override
public Object doInRe
dis(Re
disConnection connection) throws DataAccessException {
connection.set("key1".getBytes(),"Re
disCallback".getBytes());
connection.hSet("hash".getBytes(),"field".getBytes(),"hvalue".getBytes());
return null;
}
});*/
//使用SessionCallback进行回调
redi
stemplate.execute(new SessionCallback() {
@Override
public Object execute(Re
disOperations operations) throws DataAccessException {
operations.opsForValue().set("key1","SessionCallback");
operations.opsForHash().put("hash","field","hvalue");
return null;
}
});
Sy
stem.out.println("Redi
stemplate测试完成,写入相应的值");
SpringApplication.run(SpringBootStartApplication.class,args);
}
}
上面的过程使用了匿名灯的方式去使用它们,Re
disCallback接口不是那么的友好,但是它可以
修改一些底层的东西,SessionCallback相对来说友好是大部分情况下推荐使用的接口。
三、Spirng Boot中配置Redis
Redis的配置集成
Spring Boot中集成Re
dis,在
配置文件中配置Re
dis服务器,在application.properties
文件中新增如下
内容
#配置Redis
spring.redis.jedis.pool.min-idle=
5
spring.redis.jedis.pool.max-active=
10
spring.redis.jedis.pool.max-idle=
10
spring.redis.jedis.pool.max-wait=
2000ms
#配置Redis服务器
spring.redis.port=
6379
spring.redis.host=
127.0.0.1
spring.redis.password=
xiaoxie
#Redis连接超时时间,单位毫秒
spring.redis.timeout=
1000ms
对于Re
dis的配置不需要使用单独的配置类进行编码进行,直接在SpringBoot的启动类中
添加如下
代码
@Autowired
private Redi
stemplate redi
stemplate = null;
//
自定义后初始化
方法
@
postconstruct
public void init(){
initRedi
stemplate();
}
//设置Redi
stemplate的序列化器
private void initRedi
stemplate() {
Re
disSerializer stringSerializer = redi
stemplate.getStringSerializer();
redi
stemplate.setKeySerializer(stringSerializer);
redi
stemplate.setValueSerializer(stringSerializer);
redi
stemplate.setHashKeySerializer(stringSerializer);
}
上面的
代码首先通过@Autowired注入由Spring Boot根据
配置文件生成的Redi
stemplate对象,然后利用Spring Bean生命周期的特性使用注解@
postconstruct自己定义后初始化
方法,
在这个
自定义的初始化
方法中对序列化器进行
修改。
在原来TestController的controller类中新增controller的
方法如下
@Autowired
Redi
stemplate redi
stemplate = null;
@RequestMapping("/re
dis")
@ResponseBody
public String testRe
dis(){
//使用SessionCallback进行回调
redi
stemplate.execute(new SessionCallback() {
@Override
public Object execute(Re
disOperations operations) throws DataAccessException {
operations.opsForValue().set("key1","SessionCallback_haha");
operations.opsForHash().put("hash","field","hvalue");
return null;
}
});
return "执行完成!";
}
运行启动类,并在浏览器中访问 http://localhost:8080/re
dis,
页面会
显示“执行完成!”,使用客户端执行re
dis命令:get key1
得到的结果是SessionCallback_haha
操作Redis数据类型
1、操作字条串和散列
新增
一个controller类com.xiaoxie.controller.Re
disController
package com.xiaoxie.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.re
dis.core.BoundHashOperations;
import org.springframework.data.re
dis.core.Redi
stemplate;
import org.springframework.data.re
dis.core.StringRedi
stemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import re
dis.clients.je
dis.Je
dis;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/re
dis")
public class Re
disController {
@Autowired
private Redi
stemplate redi
stemplate = null;
@Autowired
private StringRedi
stemplate stringRedi
stemplate = null;
@RequestMapping("/stringAndHash")
@ResponseBody
public Map<String,Object> StringAndHash(){
redi
stemplate.opsForValue().set("key1","value1");
redi
stemplate.opsForValue().set("int_key","1"); //这里使用的是JDK序列化器,写入的1不是整数,不可以运算
stringRedi
stemplate.opsForValue().set("int","1");
//使用运算
stringRedi
stemplate.opsForValue().increment("int",2);
//
获取底层Je
dis连接
Je
dis je
dis = (Je
dis) stringRedi
stemplate.getConnectionFactory().getConnection().getNativeConnection();
//减 1操作,这个命令Redi
stemplate
不支持,需要先
获取底层连接再操作
je
dis.decr("int");
Map<String,String> hash = new HashMap<>();
hash.put("field1","value1");
hash.put("field2","value2");
//存入
一个散列数据类型
stringRedi
stemplate.opsForHash().putAll("hash",hash);
//新增
一个字段
stringRedi
stemplate.opsForHash().put("hash","field3","value3");
//绑定散列操作的key
BoundHashOperations hashOperations = stringRedi
stemplate.boundHashOps("hash");
//
删除两个字段
hashOperations.delete("field1","field2");
//新增
一个字段
hashOperations.put("field4","value4");
Map<String,Object> map = new HashMap<>();
map.put("success",true);
return map;
}
}
这里
自动注入了Redi
stemplate和StringRedi
stemplate对象。
先用Redi
stemplate存入了key1,int_key,由于使用的是JDK序列化器,所以在Re
dis服务器中int_key它不是整数,而是
一个被JDK序列化后的二进制字符串,它是没有办法使用Re
dis命令进行运算的,为了
解决这个问题,使用StringRedi
stemplate对象保存了
一个键为"int"的整数,这个子就可进行接下来的运算了。
注意:Redi
stemplate是不能
支持底层所有的Re
dis命令的,所以在需要使用底层Re
dis命令的地方,先
获取到原始的Re
dis连接的Je
dis对象:Je
dis je
dis = (Je
dis) stringRedi
stemplate.getConnectionFactory().getConnection().getNativeConnection();
对于散列的操作,可以看到先
生成一个Map,然后使用putAll方式插入多个散列,同时也使用BoundHashOperations来操作同一数据。
2、操作链表
在controller的类中新增
一个方法用来对list链表进行操作
@RequestMapping("/list")
@ResponseBody
public Map<String,Object> Re
disList(){
//判断是否存在key,如果存在则
删除
if(stringRedi
stemplate.hasKey("list1")){
stringRedi
stemplate.delete("list1");
}
if(stringRedi
stemplate.hasKey("list2")){
stringRedi
stemplate.delete("list2");
}
//插入两个列表
//从左到右顺序是:v10,v8,v6,v4,v2
stringRedi
stemplate.opsForList().leftPushAll("list1","v2","v4","v6","v8","v10");
//从左到右顺序为v1,v2,v3,v4,v5,v6
stringRedi
stemplate.opsForList().rightPushAll("list2","v1","v2","v3","v4","v5","v6");
//绑定list2这个链表
BoundListOperations listOperations = stringRedi
stemplate.boundListOps("list2");
//从右边弹出
一个成员
Object result1 = listOperations.rightPop();
//
获取定位元素,Re
dis从0开始计算
Object result2 = listOperations.index(1);
//从左边插入链表
listOperations.leftPush("v0");
//求链表长度
Long size = listOperations.size();
//求链表下标区间成员,整个链表下标范围是0~size-1
List elements = listOperations.range(0,size-2);
Map<String,Object> map = new HashMap<>();
map.put("success",true);
map.put("list",elements);
map.put("list_size",elements.size());
return map;
}
这里有如下两个问题需要注意:
1、列表元素的顺序问题,是从左到右还是从右到左,这个要注意
2、下标问题,在Re
dis中是以0开始的
操作集合
在controller的类中新增
一个方法来新增对set集合的操作
@RequestMapping("/set")
@ResponseBody
public Map<String,Object> Re
disSet(){
Map<String,Object> map = new HashMap();
if(stringRedi
stemplate.hasKey("set1")){
stringRedi
stemplate.delete("set1");
}
if(stringRedi
stemplate.hasKey("set2")){
stringRedi
stemplate.delete("set2");
}
if(stringRedi
stemplate.hasKey("inter")){
stringRedi
stemplate.delete("inter");
}
if(stringRedi
stemplate.hasKey("diff")){
stringRedi
stemplate.delete("diff");
}
if(stringRedi
stemplate.hasKey("union")){
stringRedi
stemplate.delete("union");
}
stringRedi
stemplate.opsForSet().add("set1","v1","v2","v1","v3","v4","v5");
stringRedi
stemplate.opsForSet().add("set2","v2","v4","v6","v8");
//绑定set1集合
BoundS
etoperations s
etoperations = stringRedi
stemplate.boundS
etops("set1");
//
增加两个元素
s
etoperations.add("v6","v7");
//
删除三个元素
s
etoperations.remove("v0","v1","v7");
Set set1 = s
etoperations.members();
Set set2 = stringRedi
stemplate.boundS
etops("set2").members();
map.put("set1集合",set1);
map.put("set2集合",set2);
//求交集
Set inter = s
etoperations.intersect("set2");
map.put("set1与set2的交集",inter);
//求交集并用新集合inter保存
s
etoperations.intersectAndStore("set2","inter");
//求差集
Set diff = s
etoperations.diff("set2");
map.put("set1与set2的差集",diff);
//求差集并用新集合diff保存
s
etoperations.diffAndStore("set2","diff");
//求并集
Set union = s
etoperations.union("set2");
map.put("set1与set2的并集",union);
//求并集并用新集合union保存
s
etoperations.unionAndStore("set2","union");
map.put("success",true);
return map;
}
注意:对于集合set,在Re
dis中是不允许成员重复的,它在数据结构上是
一个散列表的结构,所以它是元序的。
上面的集合是无序的,为了让集合
支持排序,Re
dis提供了有序集合zset,它与无序的集合差异不大,也是一种散列表存储的方式,同时它的有序性靠的是它在数据结构中
增加一个属性score得以
支持。
Spring提供了TypedTuple接口,并且还提供了它的
默认实现类DefalutTypedTuple
在TypedTuple接口中,value保存的是有序集合的值,
score保存的是分数,Re
dis使用
分类来完成集合的排序。
在controller中新增
方法来操作zset集合
@RequestMapping("/zset")
@ResponseBody
public Map<String,Object> Re
disZset(){
if(stringRedi
stemplate.hasKey("zset1")){
stringRedi
stemplate.delete("zset1");
}
Map<String,Object> map = new HashMap<>();
Set<ZS
etoperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
for(int i=0;i<=9;i++){
//分数
double
score = i * 0.1;
//创建
一个TypedTuple对象,并存入值和分数
ZS
etoperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>("value" + i,
score);
typedTupleSet.add(typedTuple);
}
//向有序集合插入元素
stringRedi
stemplate.opsForZSet().add("zset1",typedTupleSet);
//绑定zset1
BoundZS
etoperations<String,String> zS
etoperations = stringRedi
stemplate.boundZS
etops("zset1");
//
增加一个元素
zS
etoperations.add("value10",0.25);
Set<String> setRange = zS
etoperations.range(1,6);
map.put("排序1~6的集合",setRange);
Set<String> set
score = zS
etoperations.rangeBy
score(0.2,0.6);
map.put("分数在0.2到0.6的集合",set
score);
//定义值范围
Re
disZSetCommands.Range range = new Re
disZSetCommands.Range();
range.gt("value3");//大于value3
range.lte("value8");//小于等于value8
Set<String> setLex = zS
etoperations.rangeByLex(range);
map.put("大于value3小于等于value8的集合",setLex);
//
删除元素
zS
etoperations.remove("value9","value2");
//求分数
Double
score = zS
etoperations.
score("value8");
map.put("value8的分数",
score);
//在下标区间下,按分数排序,时同返回value和
score
Set<ZS
etoperations.TypedTuple<String>> rangeSet = zS
etoperations.rangeWith
scores(1,6);
map.put("rangeWith
scores(1,6)",rangeSet);
Set<ZS
etoperations.TypedTuple<String>>
scoreSet = zS
etoperations.rangeBy
scoreWith
scores(1,6);
map.put("rangeBy
scoreWith
scores(1,6)",
scoreSet);
//按从大到小排序
Set<String> reverseSet = zS
etoperations.reverseRange(2,8);
map.put("reverseRange(2,8)",reverseSet);
map.put("success",true);
return map;
}
TypedTuple保存有序集合的元素,
默认情况下,有序集合是从小到大排序的。
四、Redis的特殊用法
Re
dis的一些常用的特殊
用法:事务
支持、流水线、发布
订阅、Lua脚本语言 ……
Redis事务
Re
dis是
支持一定事务能力的No
sql,在Re
dis中使用事务,通常的命令组合是watch... multi... exec,需要在Re
dis连接中执行多个命令,这时则可以考虑使用SessionCallback接口来实现。
watch命令可以监控Re
dis的一些键,multi命令用来开始事务,当事务开始后,客户端的命令不会马上执行而是会存放在
一个队列中(这里
调用Re
dis命令,结果都会返回null),exec命令用于执行事务,它在队列执行前会判断被watch监控的Re
dis的键数据是否发生过变化(注意:就算是赋予了与之前相同的值也会被认为是变化过的),如果它认为发生了变化,那么Re
dis会取消事务,否则就会执行事务,Re
dis在执行事务时,要么是全部执行,要么是全部不执行,而不会被其他客户端打断,这样就保证了Re
dis事务下数据的一致性。
在controller类中新增
一个事务相关的验证
方法
@RequestMapping("/multi")
@ResponseBody
public Map<String,Object> Re
disMulti(){
redi
stemplate.opsForValue().set("key1","v1");
List list = (List) redi
stemplate.execute(new SessionCallback() {
@Override
public Object execute(Re
disOperations operations) throws DataAccessException {
//设置要监控的键
operations.watch("key1");
//开启事务
operations.multi();
operations.opsForValue().set("key2","v2");
//这里
获取值返回为null
Object v2 = operations.opsForValue().get("key2");
Sy
stem.out.println("由于命令还在队列中,返回的值为null,key2-->" + v2 );
operations.opsForValue().set("key3","v3");
Object v3 = operations.opsForValue().get("key3");
Sy
stem.out.println("由于命令还在队列中,返回的值为null,key3-->" + v3 );
//执行(监控的键未变化过)或取消事务(监控的键变化过)
return operations.exec();
}
});
Sy
stem.out.println("执行事务返回结果:" + list);
Map<String,Object> map = new HashMap<>();
map.put("success",true);
return map;
}
Redis流水线
在
默认情况下,Re
dis客户端是一条一条命令发送给Re
dis服务器的,这们
性能不是很好,为了能达到批量执行命令的目的,Re
dis中提供了流水线pipline技术,这样就可以大幅度地提升Re
dis的
性能。
在controller类中新增
一个方法来测试Re
dis流水线方式读写的
性能
@RequestMapping("/pipeline")
@ResponseBody
public Map<String,Object> re
disPipeline(){
Long start = Sy
stem.currentTimeMillis(); //开始毫秒值
List list = redi
stemplate.executePipelined(new SessionCallback(){
@Override
public Object execute(Re
disOperations operations) throws DataAccessException {
for(int i=1;i<=100000;i++){
operations.opsForValue().set("pipeline_"+i,"value_"+i);
String value = (String) operations.opsForValue().get("pipeline_"+i);
if(i == 100000){
Sy
stem.out.println("命令只是进入队列,所有值为空" + value);
}
}
return null;
}
});
Long end = Sy
stem.currentTimeMillis(); //结束毫秒值
Sy
stem.out.println("执行耗时:" + (end-start) +"毫秒" );
Map<String,Object> map = new HashMap<>();
map.put("success",true);
map.put("执行耗时(毫秒)",(end-start));
return map;
}
注意,上面只是做的测试,在实际开发过程中需要同时考虑
一个问题就是内存空
间的消耗问题,对于程序它会返回
一个List对象,如果过多的命令执行返回结果都会保存到这个List当中,这样就会造成内存消耗过大,特别是一些高并发的网站很容易导致JVM内存溢出。
第二要注意的点就是,使用流水线的过程中,所有命令也只是进入队例中没有地,所以执行命令返回值也是空的。
Redis发布订阅
发布
订阅是消息的一种常用模式。
模式:Re
dis提供
一个渠道,把消息发送到这个渠道上,多个系统云监听这个渠道。
当一条消息发送到渠道,渠道会
通知它的监听者,那么监听者则可以得到这个渠道给它们的消息了,之后监听者则根据需要去处理消息。
为了能接收Re
dis渠道发送过来的消息,先要定义
一个消息监听器(MessageListener)
新增
一个类:com.xiaoxie.listener.Re
disMessageListener
package com.xiaoxie.listener;
import org.springframework.data.re
dis.connection.Message;
import org.springframework.data.re
dis.connection.MessageListener;
import org.springframework.stereotype.Component;
@Component
public class Re
disMessageListener implements MessageListener{
@Override
public void onMessage(Message message, byte[] pattern) {
//消息体
String body = new String(message.getBody());
//渠道
名称
String topic = new String(pattern);
Sy
stem.out.println(body);
Sy
stem.out.println(topic);
}
}
Re
disMessageListener类实现了接口MessageListener
方法onMessage是得到消息后的处理
方法,
方法中的message参数代表了Re
dis发送过来的消息,pattern是指的渠道
名称
这个类使用了注解@Component,这样的话在Spring Boot扫描后,会把它
自动装配到IoC容器中。
在Spring Boot的启动类中配置其它的信息使得系统可以监控Re
dis消息,
修改后的
内容如下:
@SpringBootApplication
public class SpringBootStartApplication {
@Autowired
private Redi
stemplate redi
stemplate = null;
//Re
dis连接工厂
@Autowired
private Re
disConnectionFactory connectionFactory = null;
//Re
dis消息监听器
@Autowired
private MessageListener re
disMessageListener = null;
//任务池
private ThreadPoolTaskScheduler taskScheduler = null;
/**
* 创建任务池,运行线程等待处理Re
dis的消息
* @return
*/
@Bean
public ThreadPoolTaskScheduler initTaskScheduler() {
if(taskScheduler !=null){
return taskScheduler;
}
taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(20);
return taskScheduler;
}
/**
* 定义Re
dis的监听器
* @return
*/
@Bean
public Re
disMessageListenerContainer initRe
disContainer(){
Re
disMessageListenerContainer container = new Re
disMessageListenerContainer();
//Re
dis连接工厂
container.setConnectionFactory(connectionFactory);
//设置运行任务池
container.setTaskExecutor(initTaskScheduler());
//定义监听渠道,
名称为:topic1
Topic topic = new ChannelTopic("topic1");
//使用监听器监听Re
dis的消息
container.addMessageListener(re
disMessageListener,topic);
return container;
}
//
自定义后初始化
方法
@
postconstruct
public void init(){
initRedi
stemplate();
}
//设置Redi
stemplate的序列化器
private void initRedi
stemplate() {
Re
disSerializer stringSerializer = redi
stemplate.getStringSerializer();
redi
stemplate.setKeySerializer(stringSerializer);
redi
stemplate.setHashKeySerializer(stringSerializer);
}
public static void main(String[] args) {
SpringApplication.run(SpringBootStartApplication.class,args);
}
}
在controller类中新增
一个方法用来发布消息
@RequestMapping("/sendMessage")
@ResponseBody
public Map<String,Object> sendMessage(String topic,String msg){
Map<String,Object> map = new HashMap<>();
stringRedi
stemplate.convertAndSend(topic,msg);
map.put("success",true);
return map;
}
这里要注意一下,在访问时需要同时提供两个参数topic(表示指定渠道),msg(表示消息
内容)
在客户端中使用下面的命令出可以发布消息:publish topic1 msg
使用Lua脚本
Re
dis中有很多命令,但是Re
dis提供的计算能力是比较有限的,为了增强Re
dis的计算能力,Re
dis在2.6版本后提供了Lua脚本的
支持,而且执行lua脚本在Re
dis中还具备有原子性,所以在要保证数据一致性的高并发环境中,也可以使用Re
dis的Lua语言来保证数据的一致性。
Re
dis中两种运行Lua的
方法
方法一、直接发送Lua到Re
dis服务器去执行
方法二、把Lua发送给一Re
dis,Re
dis会对Lua脚本进行缓存,然后返回
一个SHA1的32位编码回来,之后只需要发送SHA1和相关参数给Re
dis即可。
方法二中使用32位编码进行执行的
方法目的是为了在让长的Lua脚本在执行过程中不会导致网络传输瓶颈,从而提高系统
性能。
为了
支持Re
dis的Lua脚本,Spring提供了Re
disScript接口,同时也有
一个DefaultRe
disScript实现类
在controller类中新增
一个方法来执行
一个简单的Lua脚本
@RequestMapping("/lua")
@ResponseBody
public Map<String,Object> re
disLua(){
Map<String,Object> map = new HashMap<>();
DefaultRe
disScript<String> rs = new DefaultRe
disScript<>();
//设置脚本
rs.setScriptText("return 'OK,Re
dis!'");
//定义返回类型,如果不定义Spring不返回结果
rs.setResultType(String.class);
Re
disSerializer<String> stringRe
disSerializer = redi
stemplate.getStringSerializer();
//执行lua脚本
String str = (String) redi
stemplate.execute(rs,stringRe
disSerializer,stringRe
disSerializer,null);
Sy
stem.out.println(str);
String sha1 = rs.getSha1();
Sy
stem.out.println(sha1);
map.put("success",true);
return map;
}
上面的Lua脚本只是简单的返回
一个字符串,如果要执行复杂的Lua脚本并且带有相应的参数则需要按如下的方式进行处理
Lua的脚本
re
dis.call('set',KEYS[1],ARGV[1])
re
dis.call('set',KEYS[2],ARGV[2])
local str1 = re
dis.call('get',KEYS[1])
local str2 = re
dis.call('get',KEYS[2])
if str1 == str2 then
return 1
end
return 0
这个脚本使用两个键去保存两个值,然后
获取对应的值进行比较,如果比较相等则返回1,否则返回0
KEYS[1]、KEYS[2]表示客户端传递的第
一个键和第二个键
ARGV[1]、ARGV[2]表示客户端传递的第
一个值和第二个值
在controller类中新增
一个方法进行对复杂Lua的测试
@RequestMapping("/lua2")
@ResponseBody
public Map<String,Object> re
disLua2(String key1,String key2,String value1,String value2){
Map<String,Object> map = new HashMap<>();
//定义Lua脚本
String lua = "re
dis.call('set',KEYS[1],ARGV[1]) \n"
+"re
dis.call('set',KEYS[2],ARGV[2]) \n"
+"local str1 = re
dis.call('get',KEYS[1]) \n"
+"local str2 = re
dis.call('get',KEYS[2]) \n"
+"if str1 == str2 then \n"
+"return 1 \n"
+"end \n"
+"return 0 \n";
Sy
stem.out.println(lua);
//结果返回为Long
DefaultRe
disScript<Long> rs = new DefaultRe
disScript<>();
rs.setScriptText(lua);
rs.setResultType(Long.class);
//采用字符串序列化器
Re
disSerializer<String> stringRe
disSerializer = redi
stemplate.getStringSerializer();
//定义key参数
List<String> keyList = new ArrayList<>();
keyList.add(key1);
keyList.add(key2);
//传递两个参数值,其中第
一个序列化器是key的序列化器,第二个序列化器是参数的序列化器
Long result = (Long) redi
stemplate.execute(rs,stringRe
disSerializer,stringRe
disSerializer,keyList,value1,value2);
map.put("result",result);
map.put("sha1",rs.getSha1());
return map;
}
五、Spring缓存注解操作Redis
Spring提供缓存注解去操作Re
dis的目的是为了简化编程过程
在Spring使用缓存注解前,需要配置缓存管理器
缓存管理器的接口:CacheManager及相关的类,对于Re
dis则主要的类是:Re
disCacheManager
同步我们引入mybatis、
MysqL、druid
支持,配置re
dis缓存管理器
数据库中新键
一个t_user的表
CREATE TABLE `t_user` (
`id` int(12) NOT NULL AUTO_INCREMENT,
`user_name` varchar(60) NOT NULL,
`note` varchar(512) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;
表中可以随意新增多条测试记录
在application.properties
文件中我们新增如下配置
#durid的配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=org.gjt.mm.
MysqL.Driver
spring.datasource.url=jdbc:
MysqL://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=root
#连接池的
属性
spring.datasource.druid.initial-size=15
spring.datasource.druid.max-active=100
spring.datasource.druid.min-idle=15
spring.datasource.druid.max-wait=60000
spring.datasource.druid.keep-alive=true
#数据源
默认的隔离级别"读写提交"
spring.datasource.druid.default-transaction-isolation=2
#mybatis配置
mybatis.mapper-locations=cla
sspath:mybatis/mapper/*.xml
mybatis.type-aliases-package=com.xiaoxie.pojo
#日志配置为DEBUG级别
logging.level.root=DEBUG
logging.level.org.springframework=DEBUG
logging.level.org.org.mybatis=DEBUG
#Re
dis缓存管理器配置
spring.cache.type=RE
dis
spring.cache.cache-names=re
disCache
新增
实体类:com.xiaoxie.pojo.User
package com.xiaoxie.pojo;
import org.apache.ibatis.type.Alias;
import java.io.Serializable;
@Alias("user")
public class User implements Serializable {
private Long id;
private String userName;
private String note;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
新增
一个dao接口:com.xiaoxie.dao.UserDao
package com.xiaoxie.dao;
import com.xiaoxie.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.P
aram;
import java.util.List;
@Mapper
@Component
public interface UserDao {
/**
*
获取单个
用户
* @p
aram id
* @return
*/
User getUser(Long id);
/**
* 保存
用户
* @p
aram user
* @return
*/
int insertUser(User user);
/**
*
修改用户
* @p
aram user
* @return
*/
int updateUser(User user);
/**
* 查找
用户
* @p
aram userName
* @p
aram note
* @return
*/
List<User> findUsers(@P
aram("userName") String userName,@P
aram("note") String note);
/**
*
删除用户
* @p
aram id
* @return
*/
int deleteUser(Long id);
}
配置对应的mybatis的xml
配置文件,根据application.properties的配置我们在resources/mybatis/mapper下新增
一个配置文件userMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaoxie.dao.UserDao">
<select id="getUser" p
arameterType="long" resultType="user">
select id,user_name as userName,note from t_user where id=#{id}
</select>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id" p
arameterType="user">
insert into t_user(user_name,note) VALUES (#{userName},#{note})
</insert>
<update id="updateUser">
update t_user
<set>
<if test="userName!=null">user_name=#{userName},</if>
<if test="note!=null">note=#{note}</if>
</set>
where id=#{id}
</update>
<select id="findUsers" resultType="user">
select id,user_name as userName,note from t_user
<where>
<if test="userName != null">
and user_name=#{userName}
</if>
<if test="note!=null">
and note=#{note}
</if>
</where>
</select>
<delete id="deleteUser" p
arameterType="long">
DELETE from t_user where id=#{id}
</delete>
</mapper>
新增service接口:com.xiaoxie.service.UserService
package com.xiaoxie.service;
import com.xiaoxie.pojo.User;
import org.apache.ibatis.annotations.P
aram;
import java.util.List;
public interface UserService {
/**
*
获取单个
用户
* @p
aram id
* @return
*/
User getUser(Long id);
/**
* 保存
用户
* @p
aram user
* @return
*/
User insertUser(User user);
/**
*
修改用户
* @p
aram id
* @p
aram userName
* @return
*/
User updateUserName(Long id ,String userName);
/**
* 查找
用户
* @p
aram userName
* @p
aram note
* @return
*/
List<User> findUsers(String userName, String note);
/**
*
删除用户
* @p
aram id
* @return
*/
int deleteUser(Long id);
}
新增service实现类:com.xiaoxie.service.UserServiceImpl
package com.xiaoxie.service;
import com.xiaoxie.dao.UserDao;
import com.xiaoxie.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cache
evict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao = null;
@Override
@Transactional //开启事务
@Cacheable(value = "re
disCache",key = "'re
dis_user_'+#id")
public User getUser(Long id) {
return userDao.getUser(id);
}
@Override
@Transactional
@CachePut(value = "re
disCache",key = "'re
dis_user_'+#result.id")
public User insertUser(User user) {
userDao.insertUser(user);
return user;
}
@Override
@Transactional
@CachePut(value = "re
disCache",condition = "#result!='null'",key ="'re
dis_user_' + #id")
public User updateUserName(Long id, String userName) {
//
在这里调用getUser,这个
方法的缓存注解失效
User user = this.getUser(id); //这里不会走缓存,
查询数据库
if(user == null){
return null;
}
user.setUserName(userName);
userDao.updateUser(user);
return user;
}
//这里命中率低,不采用缓存机制
@Override
@Transactional
public List<User> findUsers(String userName, String note) {
return userDao.findUsers(userName,note);
}
//移除缓存
@Override
@Transactional
@Cache
evict(value = "re
disCache",key ="'re
dis_user_' + #id",beforeInvocation = false)
public int deleteUser(Long id) {
return userDao.deleteUser(id);
}
}
关于上面的Service,在
方法上我们看到有如下几个注解是关于缓存的
@CachePut:表示把
方法结果返回存放到缓存中
@Cacheable:表示从缓存中通过定义的键
查询,如果可以
查询到数据则返回,否则执行这个
方法返回数据,并把数据结果保存到缓存中
@Cache
evict:通过定义的键移动缓存,beforeInvocation配置项可以配置
一个boolean值,表示在
方法之前或者之后移除缓存,
默认值为false(在
方法之后把缓存移除)
在上面
方法中的注解都配置了value="re
disCache",它是表示引用Spring Boot中配置的“re
disCache”这个缓存
键配置项key配置的值使用了Spring EL表达式,如key = "'re
dis_user_' + #id",这其中的#id表示参数,它通过参数
名称来做匹配,有这样的配置则要求
方法中存在
一个名称为id的参数。而在insertUser
方法中我们看到key的值如下:key = "'re
dis_user_'+#result.id",这里的#result表示返回的结果对象,#result.id则表示取出返回结果对象中的id
属性值。
updateUserName
方法里先
调用了getUser
方法(同
一个service中的
方法),则不会使用缓存,这也是合理的,对于更新这种操作直接使用缓存是有风险的所有尽量要从
数据库中去
查询最新的数据再做操作。
findUsers
方法,这个
方法没有使用缓存,它不使用缓存的原因是因为随着
用户的
查询条件的变化,会导致命中率很低,这个时候使用缓存除了消耗资源外对系统
性能的提升不会有大的改进。
新增
一个controller类:com.xiaoxie.controller.UserController,同时新增getUser
方法
package com.xiaoxie.controller;
import com.xiaoxie.pojo.User;
import com.xiaoxie.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService = null;
@RequestMapping("/getUser")
@ResponseBody
public User getUser(Long id){
return userService.getUser(id);
}
}
修改SpringBoot的启动类,让它
支持缓存同时mybatis的扫描配置
在类上
添加如下两个注解
@EnableCaching
@MapperScan(basePackages = "com.xiaoxie.dao",annotationClass = Mapper.class)
运行SpringBoot的启动类,在浏览器中访问地址:http://localhost:8080/user/gtUser?id=2
controller中会
调用service中的getUser
方法,会
获取User并在re
disCache中做缓存
在re
dis客户端中运行命令:keys *
返回的结果:"re
disCache::re
dis_user_2"
如上的返回结果表示已经把
查询返回的结果在re
dis中缓存起来了。
在UserController类中新增
方法insertUser
@RequestMapping("/insertUser")
@ResponseBody
public User insertUser(String userName,String note){
User user = new User();
user.setUserName(userName);
user.setNote(note);
userService.insertUser(user);
return user;
}
运行SpringBoot的启动类,在浏览器中访问地址:http://localhost:8080/user/insertUser?userName=赵云¬e=常山赵子龙
在re
dis客户端中运行命令:keys *
返回的结果:
1) "re
disCache::re
dis_user_2"
2) "re
disCache::re
dis_user_24"
在UserController类中新增
方法findUsers
@RequestMapping("/findUsers")
@ResponseBody
public List<User> findUsers(String userName,String note){
return userService.findUsers(userName,note);
}
运行SpringBoot的启动类,在浏览器中访问地址:http://localhost:8080/user/findUsers?userName=王五¬e=来啊
运行完成后在re
dis客户端中运行命令:keys *
返回的结果:
1) "re
disCache::re
dis_user_2"
2) "re
disCache::re
dis_user_24"
从上面返回的结果可以看出并没有在re
dis中新增缓存
内容
在UserController类中新增
方法updateUserName
方法
@RequestMapping("/updateUserName")
@ResponseBody
public Map<String,Object> updateUserName(Long id,String userName){
Map<String,Object> map = new HashMap<>();
User user = userService.updateUserName(id, userName);
boolean flag = user != null;
String message = flag?"更新成功":"更新失败";
map.put("success",flag);
map.put("message",message);
return map;
}
运行SpringBoot的启动类,在浏览器中访问地址:http://localhost:8080/user/updateUserName?id=10&UserName=赵六六
运行完成后在re
dis客户端中运行客户:keys *
返回的结果如下:
1) "re
disCache::re
dis_user_2"
2) "re
disCache::re
dis_user_10"
3) "re
disCache::re
dis_user_24"
从上面返回的结果可以看出来,被更新的那个记录已经被re
dis缓存起来了
在UserController类中新增
方法deleteUser
方法
@RequestMapping("/deleteUser")
@ResponseBody
public Map<String,Object> deleteUser(Long id){
int result = userService.deleteUser(id);
boolean flag = result==1;
String message = flag?"
删除成功":"
删除失败";
Map<String,Object> map = new HashMap<>();
map.put("success",flag);
map.put("message",message);
return map;
}
脏数据问题
脏数据指的是缓存中的数据与实现
数据库中的数据不一致的情况。
一般来说,对于读操作是可以允许非实时的数据的,它与实时数据可以存在一定的延迟,但是
一个脏数据一致存在则就会造成失真严重的问题。要
解决这个问题一般的做法是规定
一个时间,让缓存失效,在Re
dis中也可以设置超时的时间,当缓存超过超时时间后,则应用不再可以从缓存中获了数据,只能从
数据库中重新
获取最新的数据,如果系统对于实时性要求比较高则可以把缓存时间设置小一些。但是这样会加大缓存的刷新时间,
增加数据库压力。
对于写操作,要特别谨慎,考虑从
数据库中先读取最新数据,然后再更新数据,避免将缓存的脏数据写入
数据库中,从而导致出现业务问题。
自定义缓存管理器
Spring中有两种
自定义缓存管理器
1、通过
修改配置文件中的配置来
自定义
2、不使用Spring Boot自己
生成的方式而使用自己的
代码来创建缓存管理器(当需要比较多的自己定义时,推荐使用)
通过修改配置文人年中的配置来自定义
修改application.properties
#Re
dis缓存管理器配置
spring.cache.type=RE
dis
spring.cache.cache-names=re
disCache
#禁用前缀
spring.cache.re
dis.use-key-prefix=false
#
支持保存空值
#spring.cache.re
dis.cache-null-values=true
#
自定义前缀,如果禁用了前缀则这里设置前缀不生效
#spring.cache.re
dis.key-prefix=XX
#定义超时时间,单位是毫秒
spring.cache.re
dis.time-to-live=60000ms
这个时候我们再去getUser,写到re
dis缓存中的键则没有了re
disCache::这个前缀了,而且新
生成的键是存在超时时
间的,上面的配置是60秒,超过这个时间缓存则会清除掉。
自定义缓存管理器
新增
一个con
fig类:com.xiaoxie.con
fig.Re
disCacheCon
fig
package com.xiaoxie.con
fig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Con
figuration;
import org.springframework.data.re
dis.cache.Re
disCacheCon
figuration;
import org.springframework.data.re
dis.cache.Re
disCacheManager;
import org.springframework.data.re
dis.cache.Re
disCacheWriter;
import org.springframework.data.re
dis.connection.Re
disConnectionFactory;
import org.springframework.data.re
dis.serializer.JdkSerializationRe
disSerializer;
import org.springframework.data.re
dis.serializer.Re
disSerializationContext;
import java.time.Duration;
@Con
figuration
public class Re
disCacheCon
fig {
//注入连接工厂,由Spring Boot
自动配置生成
@Autowired
private Re
disConnectionFactory connectionFactory = null;
//
自定义Re
dis缓存管理器
@Bean(name = "re
disCacheManager")
public Re
disCacheManager initRe
disCacheManage(){
//Re
dis加锁的写入器
Re
disCacheWriter writer = Re
disCacheWriter.lockingRe
disCacheWriter(connectionFactory);
//启用Re
dis缓存的
默认设置
Re
disCacheCon
figuration con
figuration = Re
disCacheCon
figuration.defaultCacheCon
fig();
//设置JDK序列化器
con
figuration = con
figuration.serializeValuesWith(Re
disSerializationContext.SerializationPair.fromSerializer(
new JdkSerializationRe
disSerializer()
));
//禁用前缀
con
figuration = con
figuration.
disableKeyPrefix();
//设置1分钟超时
con
figuration = con
figuration.entryTtl(Duration.ofMinutes(1));
//创建Re
dis缓存管理器
Re
disCacheManager re
disCacheManager = new Re
disCacheManager(writer,con
figuration);
return re
disCacheManager;
}
}
这个配置类的目的就是返回
一个Bean的
名称为re
disCacheManager的缓存管理器
把
配置文件的对缓存管理器的配置注释掉,使用getUser进行测发现,
自定义的缓存管理器一样是生效的。