在Spring Boot中配置和使用Redis
配置Redis服务器,只需要在配置文件application.properties中加入:
在Spring Boot中配置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=2000
#配置Redis服务器属性
spring.redis.port=6379
spring.redis.host=192.168.11.131
spring.redis.password=123456
#Redis连接超时时间,单位毫秒
spring.redis.timeout=1000
配置连接池和服务器的属性,用以连接Redis服务器,这样Spring Boot的自动装配机制就会读取这些配置来生成有关Redis的操作对象,它会自动生成RedisConnectionFactory、RedisTemplate、StringRedisTemplate等常用的Redis对象。
RedisTemplate会默认使用JdkSerializationRedisSerializer进行序列化键值,此时Redis服务器存入的便是一个经过序列化后的特殊字符串,对于我们跟踪并不是很友好。
考虑使用StringRedisTemplate,能够支持字符串,但不能支持Java对象的存储,为了克服这个问题,可以通过设置RedisTemplate的序列化器来处理。
在Spring Boot的启动文件中修改RedisTemplate的序列化器:
package com.springboot.chapter7.main;
/**** imports ****/
@SpringBootApplication(scanBasePackages = "com.springboot.chapter7")
public class Chapter7Application {
// 注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate = null;
// 定义自定义后初始化方法
@PostConstruct
public void init() {
initRedisTemplate();
}
// 设置RedisTemplate的序列化器
private void initRedisTemplate() {
RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
}
......
}
首先通过@Autowired注入由Spring Boot根据配置生成的RedisTemplate对象,
然后利用Spring Bean生命周期的特性使用注解@PostConstruct自定义后初始化方法。
方法里,把RedisTemplate中的键序列化器修改为StringRedisSerializer。因为之前我们讨论过,在RedisTemplate中它会默认地定义了一个StringRedisSerializer对象,
所以这里我并没有自己创建一个新的StringRedisSerializer对象,而是从RedisTemplate中获取。
然后把RedisTemplate关于键和其散列数据类型的filed都修改为了使用StringRedisSerializer进行序列化,这样Redis服务器上得到的键和散列的field就都以字符串存储了。
操作Redis字符串和散列数据:
@Controller
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisTemplate redisTemplate = null;
@Autowired
private StringRedisTemplate stringRedisTemplate = null;
@RequestMapping("/stringAndHash")
@ResponseBody
public Map testStringAndHash() {
redisTemplate.opsForValue().set("key1", "value1");
// 注意这里使用了JDK的序列化器,所以Redis保存时不是整数,不能运算
redisTemplate.opsForValue().set("int_key", "1");
stringRedisTemplate.opsForValue().set("int", "1");
//设置超时时间
stringRedisTemplate.opsForValue().set("name","aa",2, TimeUnit.MINUTES);
//将对象转为json字符串来存储
AppUserEntity userEntity = AppUserEntity.builder().name("aa").build();
stringRedisTemplate.opsForValue().set("user", JSONObject.toJSONString(userEntity));
stringRedisTemplate.opsForValue().get("user");
// 使用运算
stringRedisTemplate.opsForValue().increment("int", 1);
Map hash = new HashMap();
hash.put("field1", "value1");
hash.put("field2", "value2");
// 存入一个散列数据类型
stringRedisTemplate.opsForHash().putAll("hash", hash);
// 新增一个字段
stringRedisTemplate.opsForHash().put("hash", "field3", "value3");
// 绑定散列操作的key,这样可以连续对同一个散列数据类型进行操作
BoundHashOperations hashOps = stringRedisTemplate.boundHashOps("hash");
// 删除两个字段
hashOps.delete("field1", "field2");
// 新增一个字段
hashOps.put("field4", "value5");
Map map = new HashMap();
map.put("success", true);
return map;
}
....
}
这里的@Autowired注入了Spring Boot为我们自动初始化RedisTemplate和StringRedisTemplate对象。
看到testStringAndHash方法,首先是存入了一个“key1”的数据,然后是“int_key”。但是请注意这个“int_key”存入到Redis服务器中,因为采用了JDK序列化器,所以在Redis服务器中它不是整数,而是一个被JDK序列化器序列化后的二进制字符串,没有办法使用Redis命令进行运算的。StringRedisTemplate对象保存的键为“int”的整数,因为使用了String序列化器,可以进行运算。
然后是操作散列数据类型,在插入多个散列的field时可以采用Map,然后为了方便对同一个数据操作,这里代码还获取了BoundHashOperations对象进行操作,这样对同一个数据操作就方便许多了。
列表也是常用的数据类型。在Redis中列表是一种链表结构,查询性能不高,而增删节点的性能高,存在从左到右或者从右到左的操作。
操作列表(链表):
@RequestMapping("/list")
@ResponseBody
public Map testList() {
// 插入两个列表,注意它们在链表的顺序
// 链表从左到右顺序为v10,v8,v6,v4,v2
stringRedisTemplate.opsForList().leftPushAll(
"list1", "v2", "v4", "v6", "v8", "v10");
// 链表从左到右顺序为v1,v2,v3,v4,v5,v6
stringRedisTemplate.opsForList().rightPushAll(
"list2", "v1", "v2", "v3", "v4", "v5", "v6");
// 绑定list2链表操作
BoundListOperations listOps = stringRedisTemplate.boundListOps("list2");
// 从右边弹出一个成员
Object result1 = listOps.rightPop();
// 获取定位元素,Redis从0开始计算,这里值为v2
Object result2 = listOps.index(1);
// 从左边插入链表
listOps.leftPush("v0");
// 求链表长度
Long size = listOps.size();
// 求链表下标区间成员,整个链表下标范围为0到size-1,这里不取最后一个元素
List elements = listOps.range(0, size-2);
Map map = new HashMap();
map.put("success", true);
return map;
}
上述操作是基于StringRedisTemplate的,保存到Redis服务器的都是字符串类型,有两点需要注意:列表元素的顺序问题,是从左到右还是从右到左;下标问题,在Redis中是以0开始的。
接着是集合,它在Redis中是不允许成员重复,数据结构上是一个散列表的结构,所以它是无序的,对于两个或者以上的集合,Redis还提供了交集、并集和差集的运算。
操作集合:
@RequestMapping("/set")
@ResponseBody
public Map testSet() {
// 请注意:这里v1重复两次,因为集合不允许重复,所以只是插入5个成员到集合中
stringRedisTemplate.opsForSet().add("set1",
"v1","v1","v2","v3","v4","v5");
stringRedisTemplate.opsForSet().add("set2", "v2","v4","v6","v8");
// 绑定set1集合操作
BoundSetOperations setOps = stringRedisTemplate.boundSetOps("set1");
// 增加两个元素
setOps.add("v6", "v7");
// 删除两个元素
setOps.remove("v1", "v7");
// 返回所有元素
Set set1 = setOps.members();
// 求成员数
Long size = setOps.size();
// 求交集
Set inter = setOps.intersect("set2");
// 求交集,并且用新集合inter保存
setOps.intersectAndStore("set2", "inter");
// 求差集
Set diff = setOps.diff("set2");
// 求差集,并且用新集合diff保存
setOps.diffAndStore("set2", "diff");
// 求并集
Set union = setOps.union("set2");
// 求并集,并且用新集合union保存
setOps.unionAndStore("set2", "union");
Map map = new HashMap();
map.put("success", true);
return map;
}
这里在添加集合set1时,存在两个v1一样的元素。因为集合不允许重复。
在一些网站中,经常会有排名,如最热门的商品或者最大的购买买家,都是常常见到的场景。对于这类排名,刷新往往需要及时,也涉及较大的统计,如果使用数据库会太慢。
为了支持集合的排序,Redis还提供了有序集合(zset)。有序集合与集合的差异并不大,它也是一种散列表存储的方式,同时它的有序性只是靠它在数据结构中增加一个属性——score(分数)得以支持。为了支持这个变化,Spring提供了TypedTuple接口,它定义了两个方法,并且Spring还提供了其默认的实现类DefaultTypedTuple:
在TypedTuple接口的设计中,value是保存有序集合的值,score则是保存分数,Redis是使用分数来完成集合的排序的,这样如果把买家作为一个有序集合,而买家花的钱作为分数,
就可以使用Redis进行快速排序了:
操作有序集合:
@RequestMapping("/zset")
@ResponseBody
public Map testZset() {
Set> typedTupleSet = new HashSet<>();
for (int i=1; i<=9; i++) {
// 分数
double score = i*0.1;
// 创建一个TypedTuple对象,存入值和分数
TypedTuple typedTuple
= new DefaultTypedTuple("value" + i, score);
typedTupleSet.add(typedTuple);
}
// 往有序集合插入元素
stringRedisTemplate.opsForZSet().add("zset1", typedTupleSet);
// 绑定zset1有序集合操作
BoundZSetOperations zsetOps
= stringRedisTemplate.boundZSetOps("zset1");
// 增加一个元素
zsetOps.add("value10", 0.26);
Set setRange = zsetOps.range(1, 6);
// 按分数排序获取有序集合
Set setScore = zsetOps.rangeByScore(0.2, 0.6);
// 定义值范围
Range range = new Range();
range.gt("value3");// 大于value3
// range.gte("value3");// 大于等于value3
// range.lt("value8");// 小于value8
range.lte("value8");// 小于等于value8
// 按值排序,请注意这个排序是按字符串排序
Set setLex = zsetOps.rangeByLex(range);
// 删除元素
zsetOps.remove("value9", "value2");
// 求分数
Double score = zsetOps.score("value8");
// 在下标区间下,按分数排序,同时返回value和score
Set> rangeSet = zsetOps.rangeWithScores(1, 6);
// 在分数区间下,按分数排序,同时返回value和score
Set> scoreSet = zsetOps.rangeByScoreWithScores(1, 6);
// 按从大到小排序
Set reverseSet = zsetOps.reverseRange(2, 8);
Map map = new HashMap();
map.put("success", true);
return map;
}
TypedTuple用来保存有序集合的元素,在默认的情况下,有序集合是从小到大地排序的,按下标、分数和值进行排序获取有序集合的元素,或者连同分数一起返回,有时候还可以进行从大到小的排序,只是在使用值排序时,我们可以使用Spring为我们创建的Range类,它可以定义值的范围,还有大于、等于、大于等于、小于等于等范围定义,方便我们筛选对应的元素。
地理位置和基数不是我们常用的功能,此处忽略。