Redis - Spring Data Redis
笔记
前面的 Redis - Java整合 主要介绍了 Jedis 的使用,这是官方提供连接 Redis 的类,类似于原生 JDBC,这样有一个问题,那就是需要写很多样板代码,如创建、关闭等,而现在主流使用的是 Spring 对 Jedis 的进一步封装:RedisTemplate。
本内容先从 Spring 如何使用 Redis,再介绍 Spring Boot 使用 Redis,两者的区别就在于配置而已,API 代码依然是一样的。
顺便介绍前面 Redis 安装没有的:Windows 下安装 Redis、Docker 安装 Redis。
2022-12-11 @Young Kbt
# 介绍
# Redis 支持
Spring Data 支持的键值存储之一是 Redis。引用 Redis 项目主页:
Redis 是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串(String)、哈希表(Hash)、列表(List)、集合(Set)、有序集合(sorted set),位图,hyperloglogs 等数据类型。内置复 制、Lua 脚本、LRU 收回、事务以及不同级别磁盘持久化功能,同时通过 Redis Sentinel 提供高可用,通过 Redis Cluster 提供自动分区。
为什么要用缓存:
- 提高存储读写的性能(高性能):统计数据 MySQL,解决 MySQL 查询速度慢的问题
- 提供并发(高并发):解决 MySQL 并发不足的问题
和数据库相比:
- 操作:关系型数据库(表,通过 SQL 执行操作) ,Redis 是缓存数据库(Keyvalue,通过 Redis 命令 get、set... 执行操作)
- 作用上:
- MySQL 用于持久化的存储数据到硬盘,功能强大,但是速度较慢
- Redis 用于存储使用较为频繁的数据到缓存中,读取速度快
# 为什么使用 Spring Data Redis?
Spring Framework 是领先的全栈 Java/JEE 应用程序框架。它通过使用依赖注入、AOP 和可移植服务抽象提供了一个轻量级容器和一个非侵入式编程模型。
NoSQL 存储系统提供了经典 RDBMS 的替代方案,以实现水平可扩展性和速度。在实现方面,键值存储代表 NoSQL 空间中最大(也是最古老)的成员之一。
Spring Data Redis (SDR) 框架通过 Spring 出色的基础架构支持消除了与存储交互所需的冗余任务和样板代码,从而可以轻松编写使用 Redis 键值存储的 Spring 应用程序。
原生 Jedis 有很多的样板代码:
// 连接本地的 Redis 服务
Jedis jedis = new Jedis("localhost",6379);
System.out.println("连接成功");
try {
// 设置 redis 字符串数据
jedis.set("name", "可乐");
// 获取存储的数据并输出
System.out.println("redis 存储的字符串为: "+ jedis.get("name"));
}
finally {
jedis.close();
}
2
3
4
5
6
7
8
9
10
11
12
spring-data-redis 提供了如下功能:
- 连接池自动管理,RedisTemplate 为执行各种 Redis 操作、异常转换和序列化支持提供高级抽象。
- ValueOperations:String 类型简单 K-V 操作
- SetOperations:Set 类型数据操作
- ZSetOperations:Zset 类型数据操作
- HashOperations:针对 Map 类型的数据操作
- ListOperations:针对 List 类型的数据操作
- 支持多个 Redis 驱动程序(Lettuce 和 Jedis)的低级抽象
- 支持 Repository 接口的自动实现,包括使用 @EnableRedisRepositories
- 发布订阅支持(例如用于消息驱动的 POJO 的 MessageListenerContainer)
- 针对 Jedis 客户端中大量 API 进行了归类封装,将同一类型操作封装为 operation 接口等
参考官网 https://spring.io/projects/spring-data-redis
。
# Redis 环境搭建
# 基于 Windows
下载 Redis,下载地址:https://github.com/MicrosoftArchive/redis/releases
。Windows 安装 Redis,只需要找到 .zip 下载,如:Redis-x64-3.2.100.zip
。
下载完后解压到指定目录,然后打开 cmd 进入当前的目录,执行 Redis 的启动命令:redis-server.exe redis.windows.conf
。
用 Windows 客户端,这里推荐我使用的软件:Another-Redis-Desktop-Manager。下载地址 (opens new window)
因为放在自己的服务器,宽带为 1M,下载慢,所以也可以自己去网上找。
# 基于 Linux — Docker
下载镜像
docker pull redis
创建实例并启动
# .创建目录和配置文件 redis.conf
mkdir /docker
mkdir /docker/redis
mkdir /docker/redis/conf
mkdir /docker/redis/data
# 创建 redis.conf 配置文件
touch /docker/redis/conf/redis.conf
2
3
4
5
6
7
8
创建启动容器,加载配置文件并持久化数据
docker run ‐d ‐‐privileged=true ‐p 6379:6379 ‐v /docker/redis/conf/redis.conf:/etc/redis/redis.conf ‐v /docker/r
edis/data:/data ‐‐name redis redis:latest redis‐server /etc/redis/redis.conf ‐‐appendonly yes
2
参数说明:
‐‐privileged=true
:容器内的 root 拥有真正 root 权限,否则容器内 root 只是外部普通用户权限‐v /docker/redis/conf/redis.conf:/etc/redis/redis.conf
:映射配置文件‐v /docker/redis/data:/data
:映射数据目录redis‐server /etc/redis/redis.conf
:指定配置文件启动 redis‐server 进程‐‐appendonly yes
:开启数据持久化
进入容器测试
docker exec ‐it redis redis‐cli
set test 测试
get test
2
# 快速开始
先从 Spring 配置 Redis,版本根据你当前情况选择,这里仅是演示,所以版本随意取的。
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring‐data‐redis</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!‐‐ junit5 ‐‐>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit‐jupiter</artifactId>
<version>5.6.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring‐test</artifactId>
<version>5.3.10</version>
<scope>test</scope>
</dependency>
</dependencies>
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
基于 xml
<?xml version="1.0" encoding="UTF‐8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/sp
ring‐beans.xsd">
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use‐pool="true"/>
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection‐fac
tory‐ref="jedisConnectionFactory"/>
...
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
基于 Java 的配置(推荐)
@Configuration
public class RedisConfig {
// 1. redis 的连接工厂
@Bean
public RedisConnectionFactory redisConnectionFactory(){
// 设置单机 redis 远程服务地址
RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1",6379);
// 集群
// new RedisClusterConfiguration(传入多个远程地址 ip)
// 连接池相关配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接数(根据并发量估算)
jedisPoolConfig.setMaxTotal(100);
// 最大连接数= 最大空闲数
jedisPoolConfig.setMaxIdle(100);
// 最小空闲连接数
jedisPoolConfig.setMinIdle(10);
// 最长等待时间 0 无限等待 解决线程堆积问题 最好设置
jedisPoolConfig.setMaxWaitMillis(2000);
// 在 SDR2.0+ 建议使用 JedisClientConfiguration 来设置连接池
// 默认设置了读取超时和连接超时为2秒
JedisClientConfiguration clientConfiguration =
JedisClientConfiguration.builder()
.usePooling()
.poolConfig(jedisPoolConfig).build();
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(standaloneConfiguration,clientConfi
guration);
return jedisConnectionFactory;
}
// 2. redis 模板类(工具类)
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// StringRedisSerializer 只能传入 String 类型的值(存入 Redis 之前就要转换成 String)
// redisTemplate.setDefaultSerializer(new StringRedisSerializer());
// 区分 Key 和 Value 的序列化(推荐)
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
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
关于 setDefaultSerializer
的选择,官方默认的是 jdkSerializeable 二进制字节码,即存到 Redis 之前,会将数据转成二进制字节码再存进去,所以我们去 Redis 看发现数据可读性差,且 有安全风险。
所以我们需要告诉 RedisTemplate,按照我们规定的序列化存入 Redis。setDefaultSerializer
是 Key 和 Value 统一同一个,如果细分为 Key 和 Value 的序列化,则按照上面推荐的方式序列化。这也是网上教程给的序列化方式。
测试
@SpringJUnitConfig(classes = RedisConfig.class)
public class SpringDataRedis {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testKeyBoundOperations(){
redisTemplate.boundValueOps("name").set("可乐");
Object name = redisTemplate.boundValueOps("name").get();
System.out.println(name);
}
@Test
public void testKeyTypeOperations(){
ValueOperations opsForValue = redisTemplate.opsForValue();
opsForValue.set("name","xushu");
Object name =opsForValue.get("name");
System.out.println(name);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这里没有用到 SpringBootTest 注解是因为这是 Spring,还不到 Spring Boot。
而 Junit 5 在 Spring 测试只需要这一个注解,如果是 Junit 4,则需要两个,自行百度哪两个,不过官方都推荐使用 Junit 5 了。
Redis 常用数据类型的 CRUD 操作:
@SpringJUnitConfig(classes = RedisConfig.class)
public class SpringDataRedis {
// 注入 template 对象
@Autowired
private RedisTemplate redisTemplate;
//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ Hash 类型的操作:经常用 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 值类型的操作:因为操作数量少,所以不长用 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ set 类型的操作:因为操作数量少,所以不长用 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
/**
* 存入值
*/
@Test
public void boundSetOpsAdd(){
redisTemplate.boundSetOps("nameset").add("曹操"); //放入值
redisTemplate.boundSetOps("nameset").add("刘备");
redisTemplate.boundSetOps("nameset").add("孙权");
}
/**
* 提取值
*/
@Test
public void boundSetOpsGet(){
Set names= redisTemplate.boundSetOps("nameset").members();//取出值
System.out.println(names);
}
/**
* 删除集合中的某一个值
*/
@Test
public void boundSetOpsDelete(){
redisTemplate.boundSetOps("nameset").remove("曹操");
}
/**
* 删除整个集合
*/
@Test
public void boundSetOpsDeleteAll(){
redisTemplate.delete("nameset");
}
//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐list 类型的操作:因为操作数量少,所以不长用‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
}
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
# API
RedisTemplate 提供了两大类 API,即一种在初始化绑定 key 的。一种初始化不绑定 key,后续操作再绑定 key,后者更灵活,也是最常用的。
键类型操作(初始化不绑定 key):
界面 | 描述 |
---|---|
GeoOperations | Redis 地理空间操作,例如 GEOADD,GEORADIUS |
HashOperations | Redis 哈希操作 |
HyperLogLogOperations | Redis HyperLogLog 操作,例如:PFADD、PFCOUNT |
ListOperations | Redis 列表操作 |
SetOperations | Redis 设置操作 |
ValueOperations | Redis 字符串(或值)操作 |
ZSetOperations | Redis ZSet(或排序集)操作 |
键绑定操作(初始化绑定 key):
界面 | 描述 |
---|---|
BoundGeoOperations | Redis 键绑定地理空间操作,例如 GEOADD,GEORADIUS |
BoundHashOperations | Redis 哈希键绑定操作 |
BoundKeyOperations | Redis 键绑定操作 |
BoundListOperations | Redis 列键绑定操作 |
BoundSetOperations | Redis 设置键绑定操作 |
BoundValueOperations | Redis 字符串(或值)键绑定操作 |
BoundZSetOperations | Redis ZSet(或排序集)键绑定操作 |
Redis 支持 5 种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
下面代码演示使用 键绑定操作,即带有 Bound 的 API,因为演示尽量代码少,所以初始化直接绑定 key 了,否则多出一行代码去绑定 key。
# String
string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。value 其实不仅是 String,也可以是数字,string 类型的值最大能存储 512MB。
常用命令:更多 string 命令 (opens new window)。
字符串常用操作
SET key value // 存入字符串键值对
MSET key value [key value ...] // 批量存储字符串键值对(不支持)
SETNX key value // 存入一个不存在的字符串键值对
setIfAbsent
GET key // 获取一个字符串键值
MGET key [key ...] // 批量获取字符串键值(不支持)
DEL key [key ...] // 删除一个键
redisTemplate.delete(key)
EXPIRE key seconds // 设置一个键的过期时间(秒)
username.set("可乐", 10, TimeUnit.SECONDS);
username.expire()
2
3
4
5
6
7
8
9
10
11
原子加减(原子性将整个操作视为一个整体)
INCR key // 将 key 中储存的数字值加1
DECR key // 将 key 中储存的数字值减1
INCRBY key increment // 将 key 所储存的值加上 increment
DECRBY key decrement // 将 key 所储存的值减去 decrement
2
3
4
计数器
INCR article:readcount:{文章id}
GET article:readcount:{文章id}
2
使用场景:文字的阅读量等。
代码
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisStringTest {
// 它是线程安全的
@Autowired
RedisTemplate redisTemplate;
@Test
public void test01() {
// String 类型
BoundValueOperations username = redisTemplate.boundValueOps("username");
// 10 秒过期
username.set("可乐", 10, TimeUnit.SECONDS);
}
@Test
public void test02() {
// String 类型
BoundValueOperations username = redisTemplate.boundValueOps("username");
// 设置不存在的字符串 true = 存入成功 false = 失败
System.out.println(username.setIfAbsent("冰糖"));
}
@Test
public void test03() {
// String类型
System.out.println(redisTemplate.delete("username"));
}
@Test
public void test04() {
// String 类型
BoundValueOperations count = redisTemplate.boundValueOps("count");
// 原子性累加 + 1
count.increment();
}
/**
* 秒杀 1 小时
* 预热 100 库存
*/
@Test
public void test05() {
BoundValueOperations stock = redisTemplate.boundValueOps("stock");
// 预热 100 个库存 1 小时候过期
stock.set(1, 1, TimeUnit.HOURS);
}
// 秒杀的接口
@Test
public void test06() {
BoundValueOperations stock = redisTemplate.boundValueOps("stock");
if (stock.decrement() < 0) {
System.out.println("库存不足");
} else {
System.out.println("秒杀成功");
}
}
@Test
public void test08() {
BoundValueOperations stock = redisTemplate.boundValueOps("user");
User user = new User();
user.setId(1);
user.setUsername("可乐");
stock.set(user);
}
}
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
# Hash
Redis hash 是一个键值(key => value)对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。(可以把 value 当做 Map)。
常用命令:更多 Hash 命令 (opens new window)。
HSET key field value // 存储一个哈希表 key 的键值
HSETNX key field value // 存储一个不存在的哈希表 key 的键值
HMSET key field value [field value ...] // 在一个哈希表 key 中存储多个键值对
支持根据map 批量存储
HGET key field // 获取哈希表 key 对应的 field 键值
HMGET key field [field ...] // 批量获取哈希表 key 中多个 field 键值
不支持根据指定key获取map, 仅支持获取所有键或获取所有值.keys() .values()
HDEL key field [field ...] // 删除哈希表key中的field键值
HLEN key // 返回哈希表 key 中 field 的数量
HGETALL key // 返回哈希表 key 中所有的键值
HINCRBY key field increment // 为哈希表 key 中 field 键的值加上原子增量 increment
2
3
4
5
6
7
8
9
10
11
使用场景:存储部分变更数据,如用户登录信息、做购物车列表等。相对于 string 来说,对于对象存储,不用来回进行序列化,减少内存和 CPU 的消耗。
代码
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisHashTest {
// 它是线程安全的
@Autowired
RedisTemplate redisTemplate;
// hash
@Test
public void test01() {
BoundHashOperations car = redisTemplate.boundHashOps("car");
// set
car.put("p_id", 1);
car.putIfAbsent("p_total", 10); // 不存在则存入
}
// 获取 map
@Test
public void test02() {
BoundHashOperations car = redisTemplate.boundHashOps("car");
// set
System.out.println(car.entries());
}
// 获取 map 某一项
@Test
public void test03() {
BoundHashOperations car = redisTemplate.boundHashOps("car");
// set
System.out.println(car.get("p_total"));
}
// 删除 map 某一项
@Test
public void test05() {
BoundHashOperations car = redisTemplate.boundHashOps("car");
// 删除整个 car
//redisTemplate.delete("car");
System.out.println(car.delete("p_id"));
}
// 获取 map 的长度
@Test
public void test06() {
BoundHashOperations car = redisTemplate.boundHashOps("car");
// 删除整个car
//redisTemplate.delete("car");
System.out.println(car.size());
}
// 对 map 中的某个 value 进行增量
@Test
public void test07() {
BoundHashOperations car = redisTemplate.boundHashOps("car");
car.increment("p_total", 1);
}
// 对 map 中的某个 value 进行增量
@Test
public void testcar(){
BoundHashOperations car = redisTemplate.boundHashOps("car"+99);
car.put("p_"+15,10);
car.put("p_"+16,2);
car.put("p_"+17,1);
}
// 增加购物车商品
@Test
public void testaddCar(){
BoundHashOperations car = redisTemplate.boundHashOps("car"+99);
// 增加购物车商品
// car.put("p_"+18,1);
// 增加购物车数量
//car.increment("p_"+15,1);
// 商品总数
//System.out.println(car.size());
// 删除商品
// car.delete("p_"+15);
// 获得所有的 key
System.out.println(car.keys());
// 获得所有的 value
//car.values()
}
}
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
# List
Redis List 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。(相当于 Java 的 LinkedList(链表)和 ArrayList,允许重复。
常用命令:更多 List 命令 (opens new window)。
LPUSH key value [value ...] // 将一个或多个值value插入到key列表的表头(最左边)
RPUSH key value [value ...] // 将一个或多个值value插入到key列表的表尾(最右边)
LPOP key // 移除并返回 key 列表的头元素
RPOP key // 移除并返回 key 列表的尾元素
LRANGE key start stop // 返回列表key中指定区间内的元素,区间以偏移量start和stop指定
2
3
4
5
应用场景:Redis List 的应用场景非常多,也是 Redis 最重要的数据结构之一,比如微博的关注列表,粉丝列表、热搜、top 榜单等都可以用 Redis 的 list 结构来实现。
代码
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisListTest {
// 它是线程安全的
@Autowired
RedisTemplate redisTemplate;
@Test
public void test01() {
BoundListOperations list = redisTemplate.boundListOps("list");
for (int i = 0; i < 10; i++) {
// 往尾部插入
list.rightPush(i);
// 往头部插入
// list.leftPush()
// 往尾部批量插入
// list.rightPushAll(1,2,3,4);
// list.leftPushAll(1,2,3,4);
}
}
@Test
public void test02() {
BoundListOperations list = redisTemplate.boundListOps("list");
// 范围获取
// System.out.println(list.range(0, list.size()));
// 根据下标获取某一个
System.out.println(list.index(5));
}
@Test
public void test03() {
BoundListOperations list = redisTemplate.boundListOps("list");
// 从头部删除指定数量
// list.leftPop(2);
// 从尾部删除指定数量
//list.rightPop()
// 根据值进行删除
//list.remove(1,5);
// 保留指定范围, 其余的都删掉
//list.trim(1,2);
// 数量
// list.size();
// 往指定的索引 添加/修改值
list.set(1, 10);
}
}
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
# Set
Redis Set 是 string 类型的无序集合。不允许重复。
Set 常用操作
SADD key member [member ...] // 往集合 key 中存入元素,元素存在则忽略,若 key 不存在则新建
// Java
add(V... values);
SREM key member [member ...] // 从集合 key 中删除元素
// Java
remove(Object... values);
SMEMBERS key // 获取集合 key 中所有元素
// Java
members();
SCARD key // 获取集合 key 的元素个数
// Java
size();
SISMEMBER key member // 判断 member 元素是否存在于集合 key 中
// Java
isMember(Object o);
SRANDMEMBER key [count] // 从集合 key 中选出 count 个随机元素,元素不从 key 中删除
// Java
randomMembers(long count); distinctRandomMembers(long count);
SPOP key [count] // 从集合 key 中选出 count 个元素,元素从 key 中删除
// Java
pop();
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
Set 运算操作
SINTER key [key ...] // 交集运算,set1: 123456,set2: 123789,交集:123
// Java
intersect(K key);
SINTERSTORE destination key [key ..] // 将交集结果存入新集合 destination中
// Java
intersectAndStore(K key, K destKey);
SUNION key [key ..] // 并集运算,set1: 123456,set2: 123789,并集:123456789
// Java
union(K key);
SUNIONSTORE destination key [key ...] // 将并集结果存入新集合 destination 中
// Java
unionAndStore(K key, K destKey);
SDIFF key [key ...] // 差集运算
// Java
diff(K key);
SDIFFSTORE destination key [key ...] // 将差集结果存入新集合 destination 中
// Java
diffAndStore(K keys, K destKey);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
应用场景:抽奖、共同关注。
代码
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisSetTest {
// 它是线程安全的
@Autowired
RedisTemplate redisTemplate;
// 添加
@Test
public void testAdd() {
BoundSetOperations set = redisTemplate.boundSetOps("set");
set.add(1, 2, 3, 4, 5, 6);
}
// 获取
@Test
public void testGet() {
BoundSetOperations set = redisTemplate.boundSetOps("set");
// 获取整个 set
System.out.println(set.members());
// 获取个数
System.out.println(set.size());
// 根据某个元素判断是否在 set 中
System.out.println(set.isMember(1));
// 根据count获取随机元素 不带删除
System.out.println(set.randomMembers(2));
System.out.println(set.randomMembers(2));
// 带删除
// System.out.println(set.pop());
SetOperations setOperations = redisTemplate.opsForSet();
System.out.println(setOperations.pop("set", 2));
}
// Set 运算操作
@Test
public void testAdd2() {
BoundSetOperations set = redisTemplate.boundSetOps("set2");
set.add(1, 2, 3, 7, 8, 9);
}
// 交集
@Test
public void testCacl1() {
BoundSetOperations set = redisTemplate.boundSetOps("set");
set.intersectAndStore("set2", "set3");
BoundSetOperations set3 = redisTemplate.boundSetOps("set3");
System.out.println(set3.members());
}
// 并集
@Test
public void testCacl2() {
BoundSetOperations set = redisTemplate.boundSetOps("set");
System.out.println(set.union("set2"));
}
// 差集
@Test
public void testCacl3() {
BoundSetOperations set = redisTemplate.boundSetOps("set2");
System.out.println(set.diff("set"));
}
}
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
# Sorted Set
Redis Sorted Set(Zset)与 Set 类似,区别是 Set 是无序的,Sorted Set 是有序的并且不重复的集合列表,可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。
常用命令:更多 Sorted Set 命令 (opens new window)。
ZSet 常用操作:
ZADD key score member [[score member]…] // 往有序集合 key 中加入带分值元素
ZREM key member [member …] // 从有序集合 key 中删除元素
// Java
Long remove(Object... values);
Long removeRange(long start, long end); // 根据顺序范围
Long removeRangeByScore(double min, double max); // 根据分数范围
ZSCORE key member // 返回有序集合 key 中元素 member 的分值
// Java
List<Double> score(Object... o);
Double score(Object o);
ZINCRBY key increment member // 为有序集合 key 中元素 member 的分值加上 increment
// Java
Double incrementScore(V value, double delta); // 原子添加
ZCARD key // 返回有序集合 key 中元素个数
// Java
Long size();
ZRANGE key start stop [WITHSCORES] // 正序获取有序集合 key 从 start 下标到 stop 下标的元素
// Java
Set<V> range(long start, long end); // 根据顺序范围
Set<V> rangeByScore(double min, double max); // 根据分数范围
ZREVRANGE key start stop [WITHSCORES] //倒序获取有序集合 key 从 start 下标到 stop 下标的元素
// Java
Set<V> reverseRange(long start, long end);
Set<V> reverseRangeByScore(double min, double max);
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
ZSet 集合操作 和 Set 一样差不多
ZUNIONSTORE destkey numkeys key [key ...] // 并集计算
// Java
Set<V> union(K otherKey)
ZINTERSTORE destkey numkeys key [key …] // 交集计算
// Java
Set<V> intersect(K otherKey)
2
3
4
5
6
7
应用场景:排行榜,如微博热搜。
代码
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisZSetTest {
// 它是线程安全的
@Autowired
RedisTemplate redisTemplate;
// 添加
@Test
public void testAdd() {
BoundZSetOperations zset = redisTemplate.boundZSetOps("zset");
zset.add("张三", 100);
Set<ZSetOperations.TypedTuple> tupleSet = new HashSet<>();
tupleSet.add(ZSetOperations.TypedTuple.of("李四", Double.valueOf(60)));
tupleSet.add(ZSetOperations.TypedTuple.of("王五", Double.valueOf(80)));
tupleSet.add(ZSetOperations.TypedTuple.of("赵六", Double.valueOf(70)));
zset.add(tupleSet);
}
@Test
public void testGet() {
BoundZSetOperations zset = redisTemplate.boundZSetOps("zset");
// 根据元素获取分数
System.out.println(zset.score("张三"));
// 个数
System.out.println(zset.size());
// 原子添加并且返回
System.out.println(zset.incrementScore("李四", 1));
}
@Test
public void testGetRange() {
BoundZSetOperations zset = redisTemplate.boundZSetOps("zset");
// 范围升序查询
// 升序顺序范围
System.out.println(zset.range(0, 2));
// 分数范围
System.out.println(zset.rangeByScore(60, 90));
// 带分数
System.out.println(zset.rangeWithScores(0, 2));
System.out.println(zset.rangeByScoreWithScores(60, 90));
System.out.println("--------------------");
// 范围降序查询
// 降序顺序范围
System.out.println(zset.reverseRange(0, 2));
// 降序分数范围
System.out.println(zset.reverseRangeByScore(60, 90));
// 降序带分数
System.out.println(zset.reverseRangeWithScores(0, 2));
System.out.println(zset.reverseRangeByScoreWithScores(60, 90));
}
@Test
public void testDel() {
BoundZSetOperations zset = redisTemplate.boundZSetOps("zset");
System.out.println(zset.remove("张三"));
// 按照升序范围移除
zset.removeRange(0, 1);
zset.removeRangeByScore(100, 90);
}
// 统计每日新闻 top10
@Test
public void test() {
BoundZSetOperations zset = redisTemplate.boundZSetOps("news20000101");
zset.incrementScore("守护香港", 1);
System.out.println(zset.reverseRangeWithScores(0, 9));
redisTemplate.convertAndSend("hello!", "world");
}
}
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
# Redis事务
简单介绍下 Redis 的几个事务命令:
Redis 事务四大指令: MULTI(开启事务)、EXEC(提交)、DISCARD(回滚)、WATCH。这四个指令构成了 Redis 事务处理的基础。
- MULTI 用来组装一个事务
- EXEC用来执行一个事务
- DISCARD 用来取消一个事务
- WATCH 类似于乐观锁机制里的版本号。被 WATCH 的 key 如果在事务执行过程中被并发修改,则事务失败。需要重试或取消。
SpringDataRedis 提供了 2 种事务的支持方式:
execute(SessionCallback session) 方法
<T> T execute(SessionCallback<T> session);
SessionCallback 包含了一个回调函数 execute(RedisOperations operations),在这个函数里实现以上的操作,就可以保证事务的正常使用。
通过 multi(开启事务),exec(提交事务)和 discard(回滚事务)命令控制事务。
public void testRedisTx1() {
List<Object> r = template.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().set("hxm", "9999");
// 此处打印 null,因为事务还没真正执行
System.out.println(operations.opsForValue().get("hxm"));
return operations.exec(); // 提交事务
}
});
System.out.println(r);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Transactional
的支持
另一种实现事务的是 @Transactional 注解,这种方法是把事务交由给 Spring 事务管理器进行自动管理。使用这种方法之前,跟 JDBC 事务一样,要先注入事务管理器,如果工程中已经有配置了事务管理器,就可以复用这个事务管理器,不用另外进行配置。另外,需要注意的是,跟第一种事务操作方法不一样的地方就是 RedisTemplate 的 setEnableTransactionSupport(boolean enableTransactionSupport)
方法要 set 为 true,此处贴出官方的配置框架:
@Configuration
@EnableTransactionManagement
public class RedisTxContextConfiguration {
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
// explicitly enable transaction support
template.setEnableTransactionSupport(true);// 此处必须设置为 true,不然没法实现事务管理
return template;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// jedis || Lettuce
}
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public DataSource dataSource() throws SQLException {
// ...
}
}
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
这种方法的使用方法比较简单,就在要使用事务的方法注解 @Transactional,这跟 JDBC 事务使用是一样的,这样就不用手工的执行 multi、exec 方法了,这些事务控制方法会由 Spring 事务管理器自动完成。实例如下:
@Transactional
public void testRedisTx2() {
template.opsForValue().set("name", "xushu");
System.out.println(tranRedisTemplate.keys("*"));
System.out.println(template.opsForValue().get("name")); // 此处打印为 null
}
2
3
4
5
6
7
然而,这种便利的使用方法有局限性,就是不支持只读操作,如果执行 get 之类的操作,将会返回 null,所以使用的时候要多加注意。
两种方法的对比
execute(SessionCallback session)
方法
- 事务代码块的范围灵活可控
@Transactional
注解方法
- 使用方便,代码比较优雅
- 不够灵活,事务控制范围是整个方法
综合了以上的对比,两种方法各有优缺点,但个人更偏向于使用 execute 方法。如果使用 @Transactiona
l这种注解式方法,有个建议是初始化两个 RedisTemplate,一个支持事务的,一个不支持事务的,即 enableTransactionSupport 一个设为 true,一个设为 false(默认是 false)。不然,如果用支持事务的 RedisTemplate 来进行非事务性操作时,有些地方要注意,比如要手工的关闭连接等,不然是会踩坑的。
# Redis 消息传递(发布/订阅)
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(PUBLISH)发送消息,订阅者(SUBSCRIBE\PSUBSCRIBE)接收消息。下图展示了三个频道 news-word1、news-message、sms-message,以及订阅这个频道的三个客户端之间的关系:
当有新消息通过发布者发送给频道时,这个消息就会被发送给订阅它的客户端:Redis 客户端可以订阅任意数量的频道。
在 Java 里,发布者的实现非常简单,直接调用 RedisTemplate 往某个频道发送消息即可。
难的是怎么发给订阅者,因为无法知道谁订阅了这个频道,所以我们不仅需要一个订阅者,还需要一个将订阅者和发布者关联的监听者。
监听者定义在 RedisConfig 里,在定义的过程,绑定一个或者多个频道和给监听者,当发布者发布消息时,监听者收到消息,马上往频道推送,
而订阅者需要我们自定义一个类,实现(implement)官方提供的监听者,这样这个订阅者就绑定在监听者。
# 发布消息 Publishing
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisMsgTest {
// 它是线程安全的
@Autowired
RedisTemplate redisTemplate;
// 发布者
@Test
public void test02() {
redisTemplate.convertAndSend("log","1");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 接收消息 Subscribing
订阅者
@Component
public class Subscriber implements MessageListener {
@Autowired
RedisTemplate redisTemplate;
@Override
public void onMessage(Message message, byte[] pattern) {
// 消息处理 日志记录 积分 邮件发送 ...
// Message 封装消息管道和消息内容
// 管道
byte[] channel = message.getChannel(); // key
byte[] body = message.getBody(); // value
Object channelInfo = redisTemplate.getKeySerializer().deserialize(channel);
Object bodyInfo = redisTemplate.getValueSerializer().deserialize(body);
// 消息体
System.out.println("管道:"+channelInfo+"消息内容:"+bodyInfo);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
监听器
@Configuration
@ComponentScan("com.tuling")
public class RedisConfig {
// ...
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// 设置单机redis远程服务地址
RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1", 6379);
// 连接池相关配置 (过期)
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接数(根据并发量估算)
jedisPoolConfig.setMaxTotal(100);
// 最大连接数= 最大空闲数
jedisPoolConfig.setMaxIdle(100);
// 最小空闲连接数
jedisPoolConfig.setMinIdle(10);
// 最长等待时间 0无限等待 解决线程堆积问题 最好设置
jedisPoolConfig.setMaxWaitMillis(2000);
// 在 SDR2.0+ 建议使用 JedisClientConfiguration 来设置连接池
// 默认设置了读取超时和连接超时为2秒
JedisClientConfiguration clientConfiguration =
JedisClientConfiguration.builder()
.usePooling()
.poolConfig(jedisPoolConfig).build();
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(standaloneConfiguration, clientConfiguration);
return jedisConnectionFactory;
}
// 配置在订阅者
// 消息监听者容器负责:管理线程池、消息分发,分发给对应管道的监听者
// 可以动态设置订阅者、和管道,通过这个特性在不重启服务器的情况下动态设置订阅者、和管道
@Bean
RedisMessageListenerContainer container(MessageListenerAdapter messageListenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// 设置连接工厂
container.setConnectionFactory(redisConnectionFactory());
// 设置监听者绑定的管道
List<Topic> topicList = new ArrayList<>();
topicList.add(new PatternTopic("log"));
// 第一个参数:监听者,第二个参数:绑定管道
container.addMessageListener(messageListenerAdapter, topicList);
return container;
}
/**
* 消息侦听器适配器,能将消息委托给目标侦听器方法
* @return
*/
@Bean
public MessageListenerAdapter listenerAdapter(MessageListener listener) {
return new MessageListenerAdapter(listener);
}
}
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
# Redis 发布订阅和 MQ 的区别
可靠性
Redis 没有相应的机制保证消息的可靠消费,如果发布者发布一条消息,而没有对应的订阅者的话,这条消息将丢失,不会存在内存中。
RabbitMQ 具有消息消费确认机制,如果发布一条消息,还没有消费者消费该队列,那么这条消息将一直存放在队列中,直到有消费者消费了该条消息,以此可以保证消息的可靠消费。
实时性
Redis 实时性高,Redis 作为高效的缓存服务器,所有数据都存在在服务器中,所以它具有更高的实时性。
消费者负载均衡
RabbitMQ 队列可以被多个消费者同时监控消费,但是每一条消息只能被消费一次,由于 RabbitMQ 的消费确认机制,因此它能够根据消费者的消费能力而调整它的负载。
Redis 发布订阅模式,一个队列可以被多个消费者同时订阅,当有消息到达时,会将该消息依次发送给每个订阅者。
持久性
Redis 的持久化是针对于整个 Redis 缓存的内容,它有 RDB 和 AOF 两种持久化方式,可以将整个 Redis 实例持久化到磁盘,以此来做数据备份,防止异常情况下导致数据丢失。
RabbitMQ 队列,消息都可以选择性持久化,持久化粒度更小,更灵活。
队列监控
RabbitMQ 实现了后台监控平台,可以在该平台上看到所有创建的队列的详细情况,良好的后台管理平台可以方面我们更好的使用。
Redis 没有所谓的监控平台。
总结
Redis 轻量级,低延迟,高并发,低可靠性。
RbbitMQ 重量级,高可靠,异步,不保证实时。
RbbitMQ 是一个专门的 AMQP 协议队列,他的优势就在于提供可靠的队列服务,并且可做到异步,而 Redis 主要是用于缓存的,Redis 的发布订阅模块,可用于实现及时性,且可靠性低的功能。
# Redis Repository
使用 Redis 存储库可让您在 Redis 哈希中无缝转换和存储域对象、应用自定义映射策略并使用二级索引。
好处:根据各种属性作为查询条件、更加面向对象的方式去 Redis。
限制:只能针对对象 POJO(实体类)转哈希操作。
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
template.setEnableTransactionSupport(false);
return template;
}
2
3
4
5
6
启动 Repository
在配置类上添加 @EnableRedisRepositories
,并扫描对应的 Mapper 层。
@Configuration
@ComponentScan("com.tuling")
@EnableRedisRepositories("cn.youngkbt.repository")
public class RedisConfig {
// 1. redis的连接工厂
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// 设置单机redis远程服务地址
RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1", 6379);
// 连接池相关配置 (过期)
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接数(根据并发量估算)
jedisPoolConfig.setMaxTotal(100);
// 最大连接数= 最大空闲数
jedisPoolConfig.setMaxIdle(100);
// 最小空闲连接数
jedisPoolConfig.setMinIdle(10);
// 最长等待时间 0无限等待 解决线程堆积问题 最好设置
jedisPoolConfig.setMaxWaitMillis(2000);
// 在SDR2.0+ 建议使用JedisClientConfiguration 来设置连接池
// 默认设置了读取超时和连接超时为2秒
JedisClientConfiguration clientConfiguration =
JedisClientConfiguration.builder()
.usePooling()
.poolConfig(jedisPoolConfig).build();
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(standaloneConfiguration, clientConfiguration);
return jedisConnectionFactory;
}
// 2. redis模板类(工具类)
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// StringRedisSerializer 只能传入String类型的值(存入redis之前就要转换成String)
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 改成Jackson2JsonRedisSerializer 推荐
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
return redisTemplate;
}
}
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
添加 Redis Hash 的操作实体
@RedisHash("people")
public class Person {
@Id String id;
String firstname;
String lastname;
Address address;
}
2
3
4
5
6
7
8
添加一个 Mapper 继承 CrudRepository 或者 PagingAndSortingRepository
/***
*
* CrudRepository 提供基本 CRUD
* PagingAndSortingRepository 在基本 CRUD 还提供分页还排序
*
* 实现机制:JDK 动态代理,调用对应 Jedis 命令
*/
public interface UserRepository extends PagingAndSortingRepository<User,Integer> {
}
2
3
4
5
6
7
8
9
10
操作
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisRepositoryTest {
@Autowired
UserRepository repository;
@Test
public void testAdd(){
User user = new User();
user.setId(1);
user.setUsername("可乐");
repository.save(user); // 执行自带的方法
}
@Test
public void testSelect(){
System.out.println(repository.findById(1));
}
@Test
public void testDel(){
repository.deleteById(1);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
这样 Redis 就有了 User 对象的信息。
可以理解为此时把 Redis 当作了一个关系型数据库存储对象信息。
# RedisTemplate 原理
其实就在 Jedis 的基础上进行了封装,不管是连接池、还是执行命令,最终都是通过 Jedis,只不过通过 RedisTemplate 消除了样板代码。
- 当通过 RedisTemplate 执行任何命令的 API 的时候,都会执行
execute
方法 execute
在里面就创建 JedisConnetion,就是通过 Jedis 原生 API 来进行创建- 当使用连接池就会从 Jedis 的 pool 获得 Jedis 对象,如果没有使用连接池就是 new 一个 Jedis
- 所以当执行对应的命令的 API,其实就是调用 Jedis 对应的 API
- 最终调用
jedis.close
关闭连接
总结,RedisTemplate 就是通过封装 Jedis 相关 API,来消除我们操作 Jedis 的时候这些重复的样板代码。
# SpringBoot 的 SpringDataRedis
前面也说过,先介绍 Spring 的 Redis,再介绍 Spring Boot,两者的区别在于配置,那些 API 都是一样的。
SpringBoot 会通过自动配置类帮我们配置 RedisTemplate 和 RedisConnectionFactory,部分源码:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
// ...
}
2
3
4
5
6
7
但是 RedisTemplate 它使用的是默认的序列化器,所以需要自己定义 RedisTemplate 覆盖。
这里提供一个我目前在用的配置文件(没有消息订阅配置,如果需要,和上面的配置一样):
@AutoConfiguration
@ConditionalOnClass(RedisAutoConfiguration.class)
public class RedisTemplateConfig {
private final RedisConnectionFactory redisConnectionFactory;
public RedisTemplateConfig(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
@Bean
public RedisTemplate<String, String> redisTemplateString() {
return initRedisTemplate(new StringRedisTemplate());
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
return initRedisTemplate(new RedisTemplate<>());
}
public <T> RedisTemplate<String, T> initRedisTemplate(RedisTemplate<String, T> redisTemplate) {
redisTemplate.setConnectionFactory(redisConnectionFactory);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// 解决 Redis 无法存入 LocalDateTime 等 JDK8 的时间类
JavaTimeModule javaTimeModule = new JavaTimeModule();
/*
* 新增 LocalDateTime 序列化、反序列化规则
*/
javaTimeModule
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DatePatternPlus.NORM_DATETIME_FORMATTER)) // yyyy-MM-dd HH:mm:ss
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME)) // HH:mm:ss
.addSerializer(Instant.class, InstantSerializer.INSTANCE) // Instant 类型序列化
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DatePatternPlus.NORM_DATETIME_FORMATTER)) // yyyy-MM-dd HH:mm:ss
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)) // yyyy-MM-dd
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME)) // HH:mm:ss
.addDeserializer(Instant.class, InstantDeserializer.INSTANT);// Instant 反序列化
objectMapper.registerModules(javaTimeModule);
// 使用 Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
/*
* 设置 value 的序列化规则和 key 的序列化规则
* RedisTemplate 默认序列化使用的 jdkSerializable, 存储到 Redis 会变成二进制字节码,有风险!
* 所以官网建议转成其他序列化
*/
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
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
只需要配置这个类,就可以直接使用 RedisTempalte 了。
SpringBoot 的 RedisConnectionFactory 默认使用的是 Luttuse,性能比 Jedis 好些。
前面介绍的都是 Jedis,如果想使用 Jedis 可以在 Maven 依赖排除 Luttuse,引入 Jedis 依赖即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐data‐redis</artifactId>
</dependency>
2
3
4
使用 @Resource 自动注入!,因为使用 @Autowire 注意会导致无法启动项目。