目录
在Redis官网中提供了各种语言的客户端,地址:Redis客户端下载https://www.redis.net.cn/clients/
其中Java客户端也包含很多:
标记为*的就是推荐使用的java客户端,包括:
Jedis和Lettuce:这两个主要是提供了Redis命令对应的API,方便我们操作Redis,而SpringDataRedis又对这两种做了抽象和封装,因此我们后期会直接以SpringDataRedis来学习。
Redisson:是在Redis基础上实现了分布式的可伸缩的java数据结构,例如Map、Queue等,而且支持跨进程的同步机制:Lock、Semaphore等待,比较适合用来实现特殊的功能需求。
Jedis客户端
Jedis的官网地址: https://github.com/redis/jedis
快速入门
我们先来个快速入门,首先创建一个maven项目:
引入依赖:
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
建立连接
新建一个单元测试类,内容如下:
private Jedis jedis;
@BeforeEach
void setUp() {
//1.建立连接
jedis = new Jedis("192.168.200.130", 6379);
//2.设置密码
jedis.auth("123456");
//3.选择数据库
jedis.select(0);
}
测试
@Test
void testString() {
//1.添加
String result = jedis.set("name", "tom");
System.out.println(result);
//2.查询
String name = jedis.get("name");
System.out.println(name);
}
@Test
void testHash() {
//添加
jedis.hset("user:10001", "name", "tashuo");
jedis.hset("user:10001", "age", "21");
//查询
Map<String, String> map = jedis.hgetAll("user:10001");
System.out.println(map);
}
释放资源
@AfterEach
void tearDown() {
if(jedis != null) {
jedis.close();
}
}
完整代码
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import java.util.Map;
public class JedisTest {
private Jedis jedis;
@BeforeEach
void setUp() {
//1.建立连接
jedis = new Jedis("192.168.200.130", 6379);
//2.设置密码
jedis.auth("123456");
//3.选择数据库
jedis.select(0);
}
@Test
void testString() {
//1.添加
String result = jedis.set("name", "tom");
System.out.println(result);
//2.查询
String name = jedis.get("name");
System.out.println(name);
}
@Test
void testHash() {
//添加
jedis.hset("user:10001", "name", "tashuo");
jedis.hset("user:10001", "age", "21");
//查询
Map<String, String> map = jedis.hgetAll("user:10001");
System.out.println(map);
}
@AfterEach
void tearDown() {
if(jedis != null) {
jedis.close();
}
}
}
连接池
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。
import redis.clients.jedis.*;
public class JedisConnectionFactory {
private static JedisPool jedisPool;
static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);// 最大连接数
poolConfig.setMaxIdle(8);// 最大空闲连接数
poolConfig.setMinIdle(0);// 最小空闲连接数
poolConfig.setMaxWaitMillis(1000);// 最大等待时间
// 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码
jedisPool = new JedisPool(poolConfig, "192.168.200.130", 6379, 1000, "123456");
}
// 获取Jedis对象
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
然后,我们需要对刚刚的测试代码进行修改,修改为通过连接池获取对象的写法:
首先,就是修改获取Jedis对象的方式,由构造方法获取改为通过连接池获取:
@BeforeEach
void setUp() {
//1.建立连接
//通过构造方法创建Jedis对象
//jedis = new Jedis("192.168.200.130", 6379);
//通过静态方法创建Jedis对象(通过连接池获取对象)
jedis = JedisConnectionFactory.getJedis();
//2.设置密码
jedis.auth("123456");
//3.选择数据库
jedis.select(0);
}
获取的Jedis对象不再需要销毁,而是使用完之后归还到连接池中,
@AfterEach
void tearDown() {
if(jedis != null) {
jedis.close();
}
}
代码还是那段代码,只是底层方法会判断你这个Jedis对象是通过构造方法创建的还是从连接池获取的:如果是根据构造方法生成的,会进行销毁;如果是从连接池获取的,使用完之后会归还到连接池中。底层方法如下:
public void close() {
if (this.dataSource != null) {
JedisPoolAbstract pool = this.dataSource;
this.dataSource = null;
if (this.isBroken()) {
pool.returnBrokenResource(this);
} else {
pool.returnResource(this);
}
} else {
super.close();
}
}
测试,查看效果,可知通过连接池获取Jedis对象的操作成功:
SpringDataRedis客户端
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:Spring Data Redis
提供了对不同Redis客户端的整合(Lettuce和Jedis)
提供了RedisTemplate统一API来操作Redis
支持Redis的发布订阅模型
支持Redis哨兵和Redis集群
支持基于Lettuce的响应式编程
支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
支持基于Redis的JDKCollection实现
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
快速入门
SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单。
首先,新建一个maven项目,然后按照下面步骤执行:
引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heima</groupId>
<artifactId>redis-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--common-pool-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--Jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置Redis
spring:
data:
redis:
host: 192.168.200.130
port: 6379
password: 123456
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 1000ms
注入RedisTemplate
因为有了SpringBoot的自动装配,我们可以拿来就用:
@Resource
private RedisTemplate redisTemplate;
编写测试
@Test
void testString() {
// 设置值
redisTemplate.opsForValue().set("name","她说");
// 获取值
Object name = redisTemplate.opsForValue().get("name");
System.out.println("name="+name);
}
测试结果
自定义序列化
刚刚的操作,我们是以name为她说进行存储的,但是从下图种可以看出,键和值都变成了一串字符,那么为什么会存在这种情况呢?
这是因为 Spring Data Redis 在默认情况下使用了 Java 序列化机制 来处理对象的存储,导致中文键和值被序列化为了字节流的十六进制表示。
Spring Data Redis 的默认序列化器是 JdkSerializationRedisSerializer
,它会:
将对象(包括键和值)通过 Java 自带的序列化机制转换成字节数组
- 中文在序列化后会变成对应的字节编码(以
\x
开头的十六进制形式) - 你看到的
\xAC\xED\x00\x05
是 Java 序列化的魔术头(固定标识),后面的是实际内容的字节表示
这是由SpringDataRedis客户端的默认序列化器 JdkSerializationRedisSerializer造成的,
要让我们的键值对显示中文,我们就需要修改其默认的序列化器。
我们可以自定义RedisTemplate的序列化方式,代码如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer =
new GenericJackson2JsonRedisSerializer();
// 设置Key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}
然后,就需要对我们的代码进行修改了:
上面的代码表示,键是String类型的,值是任意的;
除此之外,因为当前项目不是SpringMVC的,所以需要我们主动导入依赖来执行序列化操作,在集成了SpringMVC的项目中,这个依赖是SpringMVC底层自带的:
<!--Jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
然后我们重新运行测试:
从IDEA输出和图形化界面根据可知,键和值一切正常。
再来测试一下,Java对象能不能正常存储,首先创建一个Java对象:
(不知道为何,这里使用Lombok注解总是提示参数个数不一致,所以此处用JavaBean)
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public Integer getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(Integer age) {
this.age = age;
}
public String toString() {
return "User{name = " + name + ", age = " + age + "}";
}
}
启动测试,查看控制台输出:
查看Redis图形化界面根据查看结果:
整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。
StringRedisTemplate
自定义序列化的方式,虽然也能解决Java对象自动序列化为JSON字符串,以及在查询时能够自动把JSON反序列化为Java对象,但是这张方式记录了序列化时对应的class名称,这样带来额外的内存开销。有时候,我们要存储的对象可能还没有class名称所占的内存大,这种情况这样的方式就显得不那么友好了。
为了节省内存空间,我们可以不使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
因为存入和读取时的序列化及反序列化都是我们自己实现的,SpringDataRedis就不会将class信息写入Redis了。这种用法比较普遍,因此SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。源码如下:
省去了我们自定义RedisTemplate的序列化方式的步骤,而是直接使用:
@Resource
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testString() {
// 设置值
stringRedisTemplate.opsForValue().set("name","晴天");
// 获取值
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name="+name);
}
Redis可视化工具显示为如下图,这说明字符串类型可以直接存储:
当我们需要存储Java对象时,就需要手动序列化和反序列化了,代码如下:
@Resource
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testSaveUser() throws JsonProcessingException {
// 创建对象
User user = new User("她说", 21);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("user:200", json);
// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
// 手动反序列化
User user1 = mapper.readValue(jsonUser, User.class);
System.out.println("user1 = " + user1);
}
可知,这张方式就比自定义序列化多了手动序列化和反序列化的过程,这个过程是由代码来实现的,在Redis上也是以JSON的格式存储的,自然就不会记录序列化时对应的class名称,这种方式也是目前所采用最多的方式,因为其友好性。Redis可视化工具显示如下:
可知,未记录序列化时对应的class名称,看起来清爽不少。
上述两种方式的优缺点如下,没有绝对的好坏,但是第二种更常用,推荐使用: