在现今的互联网应用中, NoSQL 已经广为应用 , 在互联网中起到加速系统的作用。有两种 NoSQL 使用尤为广泛,那就是 Redis 和 MongoDB。本篇幅将对 Redis 和 Spring Boot 的整合做一个总结,而Spring Boot整合Redis 是通过Spring Data中的Redis模块完成的,所以再此之前还需对此模块有一个基础的认识。
Spring Boot整合Redis
引入依赖
pom文件核心配置【web启动器已经在本工程的父工程中导入】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!--不依赖Redis的客户端lettuce-->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入Redis的客户端驱动jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
整合的配置文件
spring:
cache:
type: redis
redis:
database: 0
host: localhost
port: 6379
password:
jedis:
pool:
# 连接池中最大连接数
max-active: 8
# 连接池中最大阻塞等待时间
max-wait: -1
# 连接池中最大空闲连接数
max-idle: 5
# 连接池中最小空闲连接数
min-idle: 0
# 连接超时时间
timeout: 10000
application:
name: boot2.x-data-redis
server:
servlet:
context-path: /boot2.x-data-redis
port: 8379
整合配置如下
package top.chenfu;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import javax.annotation.PostConstruct;
/**
* @Author: [email protected]
* @Description:
* @Date: 2019/9/6 11:22
*/
@SpringBootApplication
public class RedisApplication {
@Autowired
private RedisTemplate redisTemplate;
public static void main(String[] args) {
SpringApplication.run(RedisApplication.class, args);
}
/**
* 自定义初始化方法
*/
@PostConstruct
public void init() {
initRedisTemplate();
}
/**
* 设置redisTemplate的序列化器
*/
private void initRedisTemplate() {
RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
}
}
操作 Redis 数据类型
下面来增加实践案例。这里主要演示常用 Redis 数据类型(如字符串、散列、列表、集合和有序集合)的操作。因为在大部分的场景下, 并不需要很复杂地操作 Redis,而仅仅是很简单 地使用而己,也就是只需要操作一次 Redis, 这个时候使用 RedisTemplate 的操作还是比较多的。 如果需要多次执行 Redis 命令,可以选择使用 SessionCallback 或者 RedisCallback 接口 。
操作 Redis 字符串
@GetMapping("/str/{number}")
public Object testStr(@PathVariable(required = false) Number number) {
redisTemplate.opsForValue().set("s1", "1");
stringRedisTemplate.opsForValue().set("s2", "0");
if (number instanceof Number) {
redisTemplate.opsForValue().increment("s1", number.doubleValue());
stringRedisTemplate.opsForValue().increment("s2", number.doubleValue());
}
Jedis jedis = (Jedis) redisTemplate.getConnectionFactory().getConnection().getNativeConnection();
// 获取原生的redis对象,进行redisTemplate没有的操作【decr】
redisTemplate.opsForValue().increment("s4", 2);
jedis.decr("s4");
return new ResponseEntity<>(HttpStatus.OK);
}
操作散列数据
@GetMapping("/hash")
public Object testHash() {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("f1", "v1");
hashMap.put("f2", "v2");
redisTemplate.opsForHash().putAll("h1", hashMap);
stringRedisTemplate.opsForHash().putAll("sh1", hashMap);
stringRedisTemplate.opsForHash().put("sh2", "ff1", "sh2-v1");
stringRedisTemplate.opsForHash().put("sh2", "ff2", "sh2-v2");
BoundHashOperations sh2 = redisTemplate.boundHashOps("sh2");
sh2.delete("ff1");
sh2.putAll(hashMap);
sh2.put("ff3", "sh2-v3");
return new ResponseEntity<>(HttpStatus.OK);
}
操作列表List(链表)
列表也是常用的数据类型。在 Redis 中列表是一种链表结构,这就意味着查询性能不高, 而增删节点的性能高, 这是它的特性。在 Redis 中存在从左到右或者从右到左的操作。
@GetMapping("/list")
public Object testList() {
// V2,V1
redisTemplate.opsForList().leftPushAll("list1", "v1", "v2");
redisTemplate.opsForList().leftPushAll("list2", "v1", "v2");
// V2,V1,V3,V4
stringRedisTemplate.opsForList().rightPushAll("list1", "v3", "v4");
BoundListOperations list1 = stringRedisTemplate.boundListOps("list1");
System.out.println("rightPop=" + list1.rightPop());
// 获取定位元素,redis从0开始计算
System.out.println("list[1]=" + list1.index(1));
System.out.println("list size=" + list1.size());
// 求链表下标区间成员,整个链表下标范围是0到size-1
List range = list1.range(0, list1.size() - 1);
System.out.println("遍历List");
for (Object o : range) {
System.out.println(o);
}
return new ResponseEntity<>(HttpStatus.OK);
}
上述操作基本是基于 StringRedisTemplate 的,所以保存到 Redis 服务器的都是字符串类型,只是这里有两点需要注意。首先是列表元素的顺序问题,是从左到右还是从右到左, 这是容易弄糊涂的问题: 其次是下标问题,在 Redis 中是以 0 开始的,这与 Java 中 的数组类似。
集合(Set)
对于集合, 在 Redis 中是不允许成员重复的,它在数据结构上是一个散列表的结构,所以对于它而言是无序的,对于两个或者以上的集合, Redis 还提供了交集、并集和差集的运算。
@GetMapping("/set")
public Object testSet() {
stringRedisTemplate.opsForSet().add("set1", "v1", "v3", "v2", "v5");
stringRedisTemplate.opsForSet().add("set2", "v1", "v2", "v3", "v4", "v5", "v8");
stringRedisTemplate.opsForSet().add("set3", "v1", "a1", "f1", "d1", "e1");
BoundSetOperations set1 = stringRedisTemplate.boundSetOps("set1");
System.out.println("size:" + set1.size());
System.out.println("add:" + set1.add("v1", "v3", "v6", "v4"));
System.out.println("add-size:" + set1.size());
System.out.println("remove:" + set1.remove("v1", "v7"));
// 求成员数
System.out.println("remove-size:" + set1.size());
// 求交集
Set intersect = set1.intersect("set2");
System.out.println("交集=" + intersect.toString());
set1.intersectAndStore("set2", "set2inter");
// 求差集
Set set2 = set1.diff("set2");
System.out.println("diff" + set2.toString());
// 求差集,并且用新集合diff保存
set1.diffAndStore("set2", "set2diff");
// 求并集
Set union = set1.union("set2");
System.out.println("union=" + union.toString());
set1.unionAndStore("set2", "set2union");
return new ResponseEntity<>(HttpStatus.OK);
}
这里在添加集合 setl 时, 存在两个" vl "一样的元素。因为集合不允许重复,所以实际上在集合只 算是一个元素。然后可以看到对集合各类操作,在最后还有交集、差集和并集的操作,这些是集合最常用的操作。
有序集合(Set)
在一些网站中,经常会有排名,如最热门的商品或者最大的购买买家,都是常常见到的场景。 对于这类排名,刷新往往需要及时,也涉及较大的统计,如果使用数据库会太慢。为了支持集合的 排序, Redis 还提供了有序集合(zset)。有序集合与集合的差异并不大,它也是一种散列表存储的方 式,同时它的有序性只是靠它在数据结构中增加一个属性–score (分数)得以支持。为了支持这 个变化, Spring 提供了 TypedTuple 接口,它定义了两个方法,并且 Spring 还提供了其默认的实现类 DefaultTypedTuple。
在 TypedTuple 接口的设计中, value 是保存有序集合的值, score 则是保存分数, Redis 是使用分数来完成集合的排序的,这样如果把买家作为一个有序集合,而买家花的钱作为分数,就可以使用Redis 进行快速排序了。
操作 Redis 有序集合
@GetMapping("/zset")
public Object testZset() {
Set<ZSetOperations.TypedTuple<String>> typedTuples = new HashSet<>();
for (int i = 0; i < 10; i++) {
// 分数
double score = i * 0.1;
// 创建一个TypedTuple对象,存入值和分数score
ZSetOperations.TypedTuple typedTuple = new DefaultTypedTuple<String>("v" + i, score);
typedTuples.add(typedTuple);
}
Long zset1 = stringRedisTemplate.opsForZSet().add("zset1", typedTuples);
System.out.println("zset1:" + zset1);
BoundZSetOperations<String, String> zSetOps = stringRedisTemplate.boundZSetOps("zset1");
// 增加一个元素
System.out.println("size:" + zSetOps.size());
zSetOps.add("v10", 0.5);
System.out.println("size-add:" + zSetOps.size());
Set<String> range = zSetOps.range(1, -1);
System.out.println("range=" + range.toString());
// 按分数排序获取有序集合
Set<String> rangeByScore = zSetOps.rangeByScore(0.2, 0.6);
System.out.println("rangeByScore=" + rangeByScore.toString());
// 定义值的范围,注意是值!不是分数!
RedisZSetCommands.Range range1 = new RedisZSetCommands.Range();
range1.gte("v3");
range1.lte("v6");
Set<String> rangeByLex = zSetOps.rangeByLex(range1);
System.out.println("rangeByLex=" + rangeByLex.toString());
System.out.println("remove=" + zSetOps.remove("v4", "v7"));
System.out.println("v8 score=" + zSetOps.score("v8"));
// 在下标区间,按分数排序,同时返回value和score
Set<ZSetOperations.TypedTuple<String>> tuples = zSetOps.rangeWithScores(1, 10);
System.out.println("下标遍历");
for (ZSetOperations.TypedTuple<String> tuple : tuples) {
System.out.println(tuple.getValue() + "===" + tuple.getScore());
}
// 在分数区间,按分数排序,同时返回value和score
System.out.println("分数遍历");
Set<ZSetOperations.TypedTuple<String>> rangeByScoreWithScores = zSetOps.rangeByScoreWithScores(0.1, 0.5);
for (ZSetOperations.TypedTuple<String> rangeByScoreWithScore : rangeByScoreWithScores) {
System.out.println(rangeByScoreWithScore.getValue() + "===" + rangeByScoreWithScore.getScore());
}
// 按从大到小排序
Set<String> reverseRange = zSetOps.reverseRange(2, 5);
System.out.println("reverseRange=" + reverseRange.toString());
return new ResponseEntity<>(HttpStatus.OK);
}
代码中使用了 TypedTuple 保存有序集合的元素,在默认的情况下,有序集合是从小到大排序的,按下标、分数和值进行排序获取有序集合的元素,或者连同分数一起返回,有时候还可以进行从大到小的排序,只是在使用值排序时,我们可以使用 Spring 为我们创建的 Range 类,它可以定义值的范围【不是分数!!!】,还有大于、等于、大于等于、小于等于等范围定义,方便我们筛选对应的元素。
本案例中的代码:Spring Boot2.x整合Spring Data Redis