SpringBoot2.0整合Redis
相关知识
Redis 简介
Redis 是一个开源的,基于内存中的,高性能的数据存储系统,它可以用作数据库、缓存和消息中间件。 Redis 支持多种类型的数据结构,如:string、hashes、lists、sets、sortedSets等。 Redis 内置了复制(replication)、LUA脚本(Lua scripting)、事务(transactions)、磁盘持久化(persistence)、LRU驱动事件(LRU eviction)等功能。 Redis 可以通过哨兵(Sentinel)以及集群(Cluster)提供高可用性
Lettuce 和 Jedis
Lettuce 和 Jedis 都是连接 Redis Server 的客户端程序, SpringBoot2.x 之前默认使用 Jedis 作为与 Redis 进行交互的组件,SpringBoot2.x 则换成了 Lettuce(生菜)。 Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 Jedis 实例增加物理连接。 Lettuce 基于 Netty 的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问, 同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
安装 Redis
这里采用docker-compose方式安装、启动
注意目录结构
1、创建docker-compose.yml
docker-compose.yml
# docker-compose.yml文件的版本
version: "3"
# 管理的服务
services:
redis:
# 指定镜像
image: redis:4
container_name: redis
ports:
# 端口映射
- 6379:6379
volumes:
# 目录映射
- ./conf:/usr/local/etc/redis
- ./data:/data
command:
# 执行的命令
redis-server /usr/local/etc/redis/redis.conf
2、更改redis.conf
vim redis.conf
# 保护模式 关闭保护模式 否则其他机器无法连接到redis
protected-mode no
# 如需要在后台运行,把该项的值改为yes
# 改成yes时,在docker中无法启动,最后发现,将配置文件redis.conf中的“daemonize yes”这一行注释
# 掉 或者改为no,redis就顺利启动
# 了。
daemonize no
#bind:指定redis只接收来自该IP的请求,如果不设置,那么将处理所有请求,在生产环节中最好设置该项 如果需要对其他机器开放 需要注销 #bind 127.0.0.1
# bind 127.0.0.1
# requirepass:设置客户端连接后进行任何其他指定前需要使用的密码
requirepass foobared
package com.foo.bar.redis;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.foo.bar.utils.JsonUtil;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
/**
*
* @author foobar
* @version V1.0 2020/8/27 12:58 上午
*/
@DisplayName("Redis测试类")
@Slf4j
@SpringBootTest
class RedisJsonStrTest {
private static final String TEST = "TEST:JSON:";
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 测试 Redis 操作
*/
// @DisplayName("测试Redis线程安全")
// @Test
void testRedisOps() throws InterruptedException {
// 测试线程安全,程序结束查看redis中count的值是否为1000
final String key = TEST + "count";
stringRedisTemplate.delete(key);
ExecutorService executorService = Executors.newFixedThreadPool(1000);
IntStream.range(0, 1000)
.forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment(key, 1)));
TimeUnit.SECONDS.sleep(5L);
Assertions.assertEquals("1000", stringRedisTemplate.opsForValue().get(key));
}
@DisplayName("测试String对象序列化与反序列化")
@Test
void testString() {
final String expectValue = "v1";
final String key = TEST + "string-key";
stringRedisTemplate.opsForValue().set(key, expectValue);
String actualValue = stringRedisTemplate.opsForValue().get(key);
log.info("【key】= {}", actualValue);
Assertions.assertEquals(expectValue, actualValue);
}
@DisplayName("stringRedisTemplate:String对象转义问题")
@Test
void testStringEscape() {
String expect = "string-escape";
final String key = TEST + "string-escape";
stringRedisTemplate.opsForValue().set(key, expect);
String actual = stringRedisTemplate.opsForValue().get(key);
// 手工插入的非json数据 采用GenericJackson2JsonRedisSerializer时,序列化失败
String serverInsert = stringRedisTemplate.opsForValue().get(TEST + "manualInsertValue");
log.info("serverInsert:{}", serverInsert);
Assertions.assertEquals(expect, actual);
}
@DisplayName("stringRedisTemplate:String对象转义问题")
@Test
void testStringEscape2() {
String expect = "string-escape";
final String key = TEST + "string-escape2";
stringRedisTemplate.opsForValue().set(key, expect);
String actual = (String)stringRedisTemplate.opsForValue().get(key);
// 手工插入的非json数据 采用GenericJackson2JsonRedisSerializer时,序列化失败
String serverInsert = (String)stringRedisTemplate.opsForValue().get(TEST + "manualInsertValue");
log.info("serverInsert:{}", serverInsert);
Assertions.assertEquals(expect, actual);
}
/**
* redis Long类型转化异常
*/
@DisplayName("测试Long-Integer对象转换异常问题")
@Test
void testLong() {
final Long expectValue = 1L;
Long.valueOf(stringRedisTemplate.opsForValue().get(TEST + "long-key"));
stringRedisTemplate.opsForValue().set(TEST + "long-key", String.valueOf(expectValue));
Long actualValue = Long.valueOf(stringRedisTemplate.opsForValue().get(TEST + "long-key"));
log.info("【key】= {}", actualValue);
Assertions.assertEquals(expectValue, actualValue);
}
@DisplayName("测试Java对象序列化与反序列化")
@Test
void testJavaObject() {
// 以下演示整合,具体Redis命令可以参考官方文档
String key = TEST + "user:1";
final User expectUser = new User();
expectUser.setId(1L);
expectUser.setName("张三");
expectUser.setActiveTime(new Date());
stringRedisTemplate.opsForValue().set(key, JsonUtil.toJsonString(expectUser));
// 反序列化丢失了毫秒 {"id":1,"name":"张三","activeTime":"2020-08-28 18:01:57"}
// 默认JsonUtil配置时间序列化格式为yyyy-MM-dd HH:mm:ss,对有毫秒需求的时间可以加上注解 @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss,SSS", timezone = "GMT+8")
User actualUser = JsonUtil.toBean(stringRedisTemplate.opsForValue().get(key), User.class);
log.info("【user】= {}", JsonUtil.toJsonString(actualUser));
Assertions.assertEquals(expectUser, actualUser);
}
@DisplayName("测试List<JavaBean>序列化与反序列化")
@Test
void testListObj() {
// 以下演示整合,具体Redis命令可以参考官方文档
String key = TEST + "LIST";
final User user = new User();
user.setId(1L);
user.setName("张三");
user.setActiveTime(new Date());
final List<User> expect = Lists.newArrayList(user);
stringRedisTemplate.opsForValue().set(key, JsonUtil.toJsonString(expect));
// 对应 String(字符串)
List<User> actual = JsonUtil.toList(stringRedisTemplate.opsForValue().get(key), User.class);
log.info("【user】= {}", actual);
Assertions.assertEquals(expect, actual);
}
@DisplayName("测试List<String>序列化与反序列化")
@Test
void testListString() {
// 以下演示整合,具体Redis命令可以参考官方文档
String key = TEST + "LIST-string";
final List<String> expect = Lists.newArrayList("user");
stringRedisTemplate.opsForValue().set(key, JsonUtil.toJsonString(expect));
// 对应 String(字符串)
List<String> actual = JsonUtil.toList(stringRedisTemplate.opsForValue().get(key), String.class);
log.info("【user】= {}", actual);
Assertions.assertEquals(expect, actual);
}
@DisplayName("测试List<Long>序列化与反序列化")
@Test
void testListLong() {
// 以下演示整合,具体Redis命令可以参考官方文档
String key = TEST + "LIST-long";
final List<Long> expect = Lists.newArrayList(1L);
stringRedisTemplate.opsForValue().set(key, JsonUtil.toJsonString(expect));
// 对应 String(字符串)
List<Long> actual = JsonUtil.toList(stringRedisTemplate.opsForValue().get(key), Long.class);
log.info("【user】= {}", actual);
Assertions.assertEquals(expect, actual);
}
@DisplayName("测试OpsForList-Long序列化与反序列化")
@Test
void testOpsForListLong() {
// 以下演示整合,具体Redis命令可以参考官方文档
String key = TEST + "OPSLIST-long";
stringRedisTemplate.opsForList().leftPush(key, "1");
stringRedisTemplate.opsForList().leftPush(key, "2");
// 对应 String(字符串)
Long actual = Long.parseLong(stringRedisTemplate.opsForList().leftPop(key));
Assertions.assertEquals(Long.valueOf("2"), actual);
Long actual2 = Long.valueOf(stringRedisTemplate.opsForList().leftPop(key));
Assertions.assertEquals(Long.valueOf("1"), actual2);
}
@DisplayName("测试OpsForList-String序列化与反序列化")
@Test
void testOpsForListString() {
// 以下演示整合,具体Redis命令可以参考官方文档
String key = TEST + "OPSLIST-string";
stringRedisTemplate.delete(key);
stringRedisTemplate.opsForList().leftPush(key, "1");
stringRedisTemplate.opsForList().leftPush(key, "2");
stringRedisTemplate.opsForList().leftPush(key, "3");
stringRedisTemplate.opsForList().leftPush(key, "4");
// 对应 String(字符串)
String actual = stringRedisTemplate.opsForList().leftPop(key);
Assertions.assertEquals("4", actual);
String actual2 = stringRedisTemplate.opsForList().leftPop(key);
Assertions.assertEquals("3", actual2);
List<String> actual3 = stringRedisTemplate.opsForList().range(key, 0L, 1L);
Assertions.assertEquals(Lists.newArrayList("2", "1"), actual3);
}
@Data
@NoArgsConstructor
private static class User implements Serializable {
private static final long serialVersionUID = 2892248514883451461L;
/**
* 主键id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 激活时间
*/
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss,SSS", timezone = "GMT+8")
private Date activeTime;
}
}
package com.foo.bar.redis;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.NumberUtils;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
/**
*
* @author foobar
* @version V1.0
* 2020/8/27 12:58 上午
*/
@DisplayName("Redis测试类")
@Slf4j
@SpringBootTest
class RedisTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 测试 Redis 操作
*/
// @DisplayName("测试Redis线程安全")
// @Test
void testRedisOps() throws InterruptedException {
// 测试线程安全,程序结束查看redis中count的值是否为1000
final String key = "TEST:count";
stringRedisTemplate.delete(key);
ExecutorService executorService = Executors.newFixedThreadPool(1000);
IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment(key, 1)));
TimeUnit.SECONDS.sleep(5L);
Assertions.assertEquals("1000", stringRedisTemplate.opsForValue().get(key));
}
@DisplayName("测试String对象序列化与反序列化")
@Test
void testString(){
final String expectValue = "v1";
final String key = "TEST:string-key";
stringRedisTemplate.opsForValue().set(key, expectValue);
String actualValue = stringRedisTemplate.opsForValue().get(key);
log.info("【key】= {}", actualValue);
Assertions.assertEquals(expectValue, actualValue);
}
@DisplayName("stringRedisTemplate:String对象转义问题")
@Test
void testStringEscape() {
String expect = "string-escape";
final String key = "TEST:string-escape";
stringRedisTemplate.opsForValue().set(key, expect);
String actual = stringRedisTemplate.opsForValue().get(key);
// 手工插入的非json数据 采用GenericJackson2JsonRedisSerializer时,序列化失败
String serverInsert = stringRedisTemplate.opsForValue().get("TEST:manualInsertValue");
log.info("serverInsert:{}", serverInsert);
Assertions.assertEquals(expect,actual);
}
@DisplayName("redisTemplate:String对象转义问题")
@Test
void testStringEscape2() {
String expect = "string-escape";
final String key = "TEST:string-escape2";
redisTemplate.opsForValue().set(key, expect);
String actual = (String) redisTemplate.opsForValue().get(key);
// 手工插入的非json数据 采用GenericJackson2JsonRedisSerializer时,序列化失败
String serverInsert = (String) redisTemplate.opsForValue().get("TEST:manualInsertValue");
log.info("serverInsert:{}", serverInsert);
Assertions.assertEquals(expect,actual);
}
/**
* redis Long类型转化异常
*/
@DisplayName("测试Long-Integer对象转换异常问题")
@Test
void testLong(){
final Long expectValue = 1L;
redisTemplate.opsForValue().set("TEST:long-key", expectValue);
Long actualValue = NumberUtils.convertNumberToTargetClass((Number) redisTemplate.opsForValue().get("TEST:long-key"), Long.class);
log.info("【key】= {}", actualValue);
Assertions.assertEquals(expectValue, actualValue);
}
@DisplayName("测试Java对象序列化与反序列化")
@Test
void testJavaObject(){
// 以下演示整合,具体Redis命令可以参考官方文档
String key = "TEST:user:1";
final User expectUser = new User();
expectUser.setId(1L);
expectUser.setName("张三");
expectUser.setActiveTime(new Date());
redisTemplate.opsForValue().set(key, expectUser);
// 对应 String(字符串)
User actualUser = (User) redisTemplate.opsForValue().get(key);
log.info("【user】= {}", actualUser);
Assertions.assertEquals(expectUser, actualUser);
}
@DisplayName("测试List<JavaBean>序列化与反序列化")
@Test
void testListObj(){
// 以下演示整合,具体Redis命令可以参考官方文档
String key = "TEST:LIST";
final User user = new User();
user.setId(1L);
user.setName("张三");
user.setActiveTime(new Date());
final List<User> expect = Lists.newArrayList(user);
redisTemplate.opsForValue().set(key, expect);
// 对应 String(字符串)
List<User> actual = (List<User>) redisTemplate.opsForValue().get(key);
log.info("【user】= {}", actual);
Assertions.assertEquals(expect, actual);
}
@DisplayName("测试List<String>序列化与反序列化")
@Test
void testListString(){
// 以下演示整合,具体Redis命令可以参考官方文档
String key = "TEST:LIST-string";
final List<String> expect = Lists.newArrayList("user");
redisTemplate.opsForValue().set(key, expect);
// 对应 String(字符串)
List<String> actual = (List<String>) redisTemplate.opsForValue().get(key);
log.info("【user】= {}", actual);
Assertions.assertEquals(expect, actual);
}
@DisplayName("测试List<Long>序列化与反序列化")
@Test
void testListLong(){
// 以下演示整合,具体Redis命令可以参考官方文档
String key = "TEST:LIST-long";
final List<Long> expect = Lists.newArrayList(1L);
redisTemplate.opsForValue().set(key, expect);
// 对应 String(字符串)
List<Long> actual = (List<Long>) redisTemplate.opsForValue().get(key);
log.info("【user】= {}", actual);
Assertions.assertEquals(expect, actual);
}
@DisplayName("测试OpsForList-Long序列化与反序列化")
@Test
void testOpsForListLong(){
// 以下演示整合,具体Redis命令可以参考官方文档
String key = "TEST:OPSLIST-long";
redisTemplate.opsForList().leftPush(key, 1L);
redisTemplate.opsForList().leftPush(key, 2L);
// 对应 String(字符串)
Long actual = NumberUtils.convertNumberToTargetClass((Number)redisTemplate.opsForList().leftPop(key), Long.class);
Assertions.assertEquals(Long.valueOf("2"), actual);
Long actual2 = NumberUtils.convertNumberToTargetClass((Number)redisTemplate.opsForList().leftPop(key), Long.class);
Assertions.assertEquals(Long.valueOf("1"), actual2);
Long actual3 = NumberUtils.convertNumberToTargetClass((Number)redisTemplate.opsForList().leftPop(key), Long.class);
log.info("actual3:{}",actual3);
}
@DisplayName("测试OpsForList-String序列化与反序列化")
@Test
void testOpsForListString(){
// 以下演示整合,具体Redis命令可以参考官方文档
String key = "TEST:OPSLIST-string";
redisTemplate.delete(key);
redisTemplate.opsForList().leftPush(key, "1");
redisTemplate.opsForList().leftPush(key, "2");
redisTemplate.opsForList().leftPush(key, "3");
redisTemplate.opsForList().leftPush(key, "4");
// 对应 String(字符串)
String actual = (String) redisTemplate.opsForList().leftPop(key);
Assertions.assertEquals("4", actual);
String actual2 = (String) redisTemplate.opsForList().leftPop(key);
Assertions.assertEquals("3", actual2);
List<Object> actual3 = redisTemplate.opsForList().range(key,0L, 1L);
Assertions.assertEquals(Lists.newArrayList("2", "1"), actual3);
}
@Data
@NoArgsConstructor
private static class User implements Serializable {
private static final long serialVersionUID = 2892248514883451461L;
/**
* 主键id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 激活时间
*/
private Date activeTime;
}
}
3、在yml文件所在的目录下执行下面命令启动容器
docker-compose up -d
4、查看容器
docker ps
docker-compose.yml文件详解
image: redis:4 表示使用redis4的最新版镜像
container_name: redis 指定容器的名字为redis
command: redis-server --requirepass 123456 指定redis的密码为123456
ports 将容器的6379端口映射到主机的16379端口上
volumes 用于指定存储卷,容器和宿主机可以通过存储卷共享文件
docker hub redis:https://hub.docker.com/_/redis