Redis的Java客户端
1.1 Redis连接
1.1.1 添加依赖
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<jedis.version>3.7.0</jedis.version>
<junit.version>5.8.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
1.1.2 简单连接
package cn.knightzz.test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
/**
* @author 王天赐
* @title: JedisTest
* @projectName redis-demo
* @description:
* @website http://knightzz.cn/
* @github https://github.com/knightzz1998
* @date 2022/5/2 16:38
*/
public class JedisTest {
private Jedis jedis;
@BeforeEach
void setUp(){
// 1. 建立连接
jedis = new Jedis();
// 2. 设置密码
// jedis.auth("");
// 3. 选择库
jedis.select(0);
}
@Test
void testString(){
// 插入数据
jedis.set("name", "张三");
// 获取插入的数据
String name = jedis.get("name");
System.out.println("name = " + name);
}
@AfterEach
void tearDown(){
// 释放资源
if (jedis != null) {
jedis.close();
}
}
}
1.2 Jedis连接池
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此通常使用Jedis连接池代替Jedis的直连方式
package cn.knightzz.factory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @author 王天赐
* @title: JedisConnectionFactory
* @projectName redis-demo
* @description:
* @website http://knightzz.cn/
* @github https://github.com/knightzz1998
* @date 2022/5/2 16:17
*/
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static {
// 配置连接池
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接
jedisPoolConfig.setMaxTotal(8);
// 最大空闲连接
jedisPoolConfig.setMaxIdle(8);
// 最小空闲连接
jedisPoolConfig.setMinIdle(0);
// 设置最长等待时间
jedisPoolConfig.setMaxWaitMillis(200);
// 创建连接池对象
jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379, 1000);
}
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
上面的代码是Redis连接工厂类, 我们使用连接池来创建连接
@BeforeEach
void setUp(){
// 1. 通过连接池建立连接
jedis = JedisConnectionFactory.getJedis();
// 2. 设置密码
// jedis.auth("");
// 3. 选择库
jedis.select(0);
}
获取Jedis对象 : jedis = JedisConnectionFactory.getJedis();
1.3 Spring整合Redis
1.3.1 SpringDataRedis介绍
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis
- 提供了对不同Redis客户端的整合(Lettuce和Jedis)
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型
- 支持Redis哨兵和Redis集群
- 支持基于Lettuce的响应式编程
- 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
- 支持基于Redis的JDKCollection实现
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
API | 返回值类型 | 说明 |
---|---|---|
redisTemplate.opsForValue() | ValueOperations | 操作String类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作Hash类型数据 |
redisTemplate.opsForList() | ListOperations | 操作List类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作Set类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作SortedSet类型数据 |
redisTemplate | 通用的命令 |
1.3.2 代码示例
先创建一个简单的maven项目
添加相关依赖, 这里需要注意的是: 由于我在父项目里指定了依赖版本, 所以这里不需要指定版本
<dependencies>
<!--Jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!--Redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
父项目依赖 :
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<jedis.version>3.7.0</jedis.version>
<junit.version>5.8.2</junit.version>
<springboot.version>2.4.3</springboot.version>
<lombok.version>1.18.20</lombok.version>
<jackson.version>2.13.1</jackson.version>
<commons-pool2.version>2.11.1</commons-pool2.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${commons-pool2.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
依赖添加完成以后, 创建启动类 :
package cn.knightzz;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author 王天赐
* @title: RedisApplicaiton
* @projectName redis-demo
* @description:
* @website http://knightzz.cn/
* @github https://github.com/knightzz1998
* @date 2022/5/2 18:43
*/
@SpringBootApplication
public class RedisApplication {
public static void main(String[] args) {
SpringApplication.run(RedisApplication.class, args);
}
}
然后添加配置文件 :
spring:
redis:
host: localhost
port: 6379
lettuce:
pool:
max-active: 8 # 最大连接
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: 100 # 连接等待时间
编写测试类测试即可 :
package cn.knightzz.test;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import javax.annotation.Resource;
/**
* @author 王天赐
* @title: RedisTest
* @projectName redis-demo
* @description:
* @website http://knightzz.cn/
* @github https://github.com/knightzz1998
* @date 2022/5/2 20:25
*/
@SpringBootTest
public class RedisTest {
@Resource
private RedisTemplate redisTemplate;
@Test
void testString(){
redisTemplate.opsForValue().set("name03", "SpringBoot");
String name03 = (String) redisTemplate.opsForValue().get("name03");
System.out.println("name03 = " + name03);
}
}
1.3.3 自定义序列化
RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的:
它的缺点很明显 :
- 可读性差
- 内存占用较大
我们可以自定义RedisTemplate的序列化方式,代码如下
package cn.knightzz.config;
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;
/**
* @author 王天赐
* @title: RedisConfig
* @projectName redis-demo
* @description:
* @website http://knightzz.cn/
* @github https://github.com/knightzz1998
* @date 2022/5/2 20:50
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
// 创建RedisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// key 和 hash key 使用string 序列化
redisTemplate.setHashKeySerializer(RedisSerializer.string());
redisTemplate.setKeySerializer(RedisSerializer.string());
// value和hashValue使用JSON序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
return redisTemplate;
}
}
自定义完成以后, 我们需要定义一个简单的实体类 :
package cn.knightzz.pojo;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private String name;
private Integer age;
}
然后把这个实体类添加到redis中
@Test
void testObject(){
User user = new User();
user.setName("张飞");
user.setAge(12);
redisTemplate.opsForValue().set("user01", user);
}
可以看到现在是通过JSON序列化, 并不是像之前那样序列化成字节码文件
1.3.4 StringRedisTemplate
尽管JSON的序列化方式可以满足我们的需求,但依然存在一些问题,如图:
为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销。
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:
package cn.knightzz.test;
import cn.knightzz.pojo.User;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.Resource;
/**
* @author 王天赐
* @title: StringRedisTemplateTest
* @projectName redis-demo
* @description:
* @website http://knightzz.cn/
* @github https://github.com/knightzz1998
* @date 2022/5/2 21:11
*/
@SpringBootTest
public class StringRedisTemplateTest {
@Resource
private StringRedisTemplate stringRedisTemplate;
// JSON工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testObject() throws JsonProcessingException {
// 准备对象
User user = new User();
user.setAge(22);
user.setName("stringRedisTemplate");
// 手动序列化成JSON
String json = mapper.writeValueAsString(user);
// 将数据写入json
stringRedisTemplate.opsForValue().set("userString", json);
// 读取数据
String userString = stringRedisTemplate.opsForValue().get("userString");
// 反序列化
User userFromJson = mapper.readValue(userString, User.class);
System.out.println("userFromJson = " + userFromJson);
}
}
如下图可以看到, 这里存储的都是字符串, 在写入和读取的时候进行序列化和反序列化
总结 :
RedisTemplate的两种序列化实践方案:
- 方案一:
自定义RedisTemplate
修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer - 方案二:
使用StringRedisTemplate
写入Redis时,手动把对象序列化为JSON
读取Redis时,手动把读取到的JSON反序列化为对象