网站移动端是什么,中国优秀设计网站有哪些,学建网站 必须学那些知识,php做电商网站的难点Redis 学习笔记 2#xff1a;Java 客户端
常见的 Redis Java 客户端有三种#xff1a;
Jedis#xff0c;优点是API 风格与 Redis 命令命名保持一致#xff0c;容易上手#xff0c;缺点是连接实例是线程不安全的#xff0c;多线程场景需要用线程池来管理连接。RedissonJava 客户端
常见的 Redis Java 客户端有三种
Jedis优点是API 风格与 Redis 命令命名保持一致容易上手缺点是连接实例是线程不安全的多线程场景需要用线程池来管理连接。Redisson在Redis基础上实现了分布式的可伸缩的java数据结构例如Map、Queue等而且支持跨进程的同步机制Lock、Semaphore等待比较适合用来实现特殊的功能需求。lettuce基于 Netty 实现支持同步/异步和响应式编程并且是线程安全的。支持 Redis 的哨兵模式、集群模式和管道模式。
Spring 对 Jedis 和 lettuce 进行了封装spring-data-redis 提供统一的 API 进行操作。
Jedis
单个连接
下面是一个简单的 Jedis 连接示例。
创建一个 mvn 工程并添加 Jedis 和 Junit 依赖
dependencygroupIdorg.junit.jupiter/groupIdartifactIdjunit-jupiter-api/artifactIdversion5.10.0/versionscopetest/scope
/dependency
dependencygroupIdredis.clients/groupIdartifactIdjedis/artifactIdversion5.0.0/version
/dependency编写一个单元测试
public class AppTests {private Jedis jedis;BeforeEachpublic void beforeEach() {jedis new Jedis(192.168.0.88, 6379);jedis.auth(123321);jedis.select(0);}Testpublic void testString() {String res jedis.set(name, Jack);System.out.println(res);res jedis.get(name);System.out.println(res);}Testpublic void testHash() {jedis.hset(user:1, name, Jack);jedis.hset(user:1, age, 18);MapString, String map jedis.hgetAll(user:1);System.out.println(map);}AfterEachpublic void afterEach() {if (jedis ! null) {jedis.close();}}
}在这个单元测试中展示了如何使用 Jedis 客户端连接 Redis并用 API 操作 String 类型和 Hash 类型的数据。基本上这些 API 的命名和使用方式与前文介绍的 Redis 命令是相似的。
连接池
创建一个 Jedis 连接池的工具类
public class JedisConnectionFactory {// Jedis 连接池private static JedisPool jedisPool;// 初始化 Jedis 连接池static {// 设置连接池配置JedisPoolConfig jedisPoolConfig new JedisPoolConfig();// 最大连接数jedisPoolConfig.setMaxTotal(8);// 最大空闲连接数jedisPoolConfig.setMaxIdle(8);// 最小空闲连接数jedisPoolConfig.setMinIdle(0);// 尝试从连接池中获取空闲连接时的等待时间如果没有空闲连接超时会产生错误jedisPoolConfig.setMaxWait(Duration.ofSeconds(5));// 创建连接池jedisPool new JedisPool(jedisPoolConfig,192.168.0.88, 6379, 1000, 123321);}/*** 返回一个空闲的 Redis 连接实例* return Redis 连接实例*/public static Jedis getJedisConnection() {return jedisPool.getResource();}
}之前的 Jedis 测试用例修改为使用连接池的版本
BeforeEach
public void beforeEach() {jedis JedisConnectionFactory.getJedisConnection();jedis.auth(123321);jedis.select(0);
}spring-data-redis
SpringData是Spring中数据操作的模块包含对各种数据库的集成其中对Redis的集成模块就叫做SpringDataRedis官网地址https://spring.io/projects/spring-data-redis
spring-data-redis 包含以下特性
提供了对不同Redis客户端的整合Lettuce和Jedis提供了RedisTemplate统一API来操作Redis支持Redis的发布订阅模型支持Redis哨兵和Redis集群支持基于Lettuce的响应式编程支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化支持基于Redis的JDKCollection实现
SpringDataRedis中提供了RedisTemplate工具类其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中 示例
创建一个 Spring 项目并添加以下依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
/dependency这里的commons-pool2是一个连接池依赖。
在appication.yml中添加以下配置
spring:data:redis:host: 192.168.0.88port: 6379password: 123321database: 0 # 默认连接的数据库lettuce:pool:max-active: 8max-idle: 8min-idle: 0max-wait: 100ms注意因为 Spring-data-redis 默认使用 lettuce 作为底层的 Redis 客户端所以这里配置的是 lettuce 的连接池。
单元测试
SpringBootTest
class SpringDataRedisDemoApplicationTests {Autowiredprivate RedisTemplate redisTemplate;Testvoid testString() {ValueOperations ops redisTemplate.opsForValue();ops.set(name, 王二);String val (String) ops.get(name);System.out.println(val);}
}这里注入RedisTemplate实例并用它实现对 Redis 的操作。
序列化和反序列化
Redis 本身只能处理字符串形式的 Key 和 Value而 RedisTemplate 默认设置的 Key 和 Value 可以是 Object 类型因此 RedisTemplate 底层实现了 Object 的序列化和反序列化这些序列化和反序列化的实现是由RedisTemplate 中的四个属性决定的
Nullable
private RedisSerializer keySerializer null;
Nullable
private RedisSerializer valueSerializer null;
Nullable
private RedisSerializer hashKeySerializer null;
Nullable
private RedisSerializer hashValueSerializer null;默认情况下这些序列化和反序列化的实现都是基于 JDK 的对象流实现的
public class DefaultSerializer implements SerializerObject {public DefaultSerializer() {}public void serialize(Object object, OutputStream outputStream) throws IOException {if (!(object instanceof Serializable)) {String var10002 this.getClass().getSimpleName();throw new IllegalArgumentException(var10002 requires a Serializable payload but received an object of type [ object.getClass().getName() ]);} else {ObjectOutputStream objectOutputStream new ObjectOutputStream(outputStream);objectOutputStream.writeObject(object);objectOutputStream.flush();}}
}
因此之前的示例中虽然在代码中是设置了一个 Key 为name的键值对但实际上在 Redis 服务器上创建的是一个\xac\xed\x00\x05t\x00\x04name这样的键一般来说我们是不能接受的。
因此我们需要自己定义一个使用特定序列化实现的RedisTemplate而不是使用默认实现
Configuration
public class WebConfig {BeanRedisTemplateString, Object redisTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplateString, Object redisTemplate new RedisTemplate();redisTemplate.setConnectionFactory(redisConnectionFactory);GenericJackson2JsonRedisSerializer jsonRedisSerializer new GenericJackson2JsonRedisSerializer();redisTemplate.setKeySerializer(RedisSerializer.string());redisTemplate.setValueSerializer(jsonRedisSerializer);redisTemplate.setHashKeySerializer(RedisSerializer.string());redisTemplate.setHashValueSerializer(jsonRedisSerializer);return redisTemplate;}
}这里对RedisTemplate使用了类型参数因为一般而言key 和 HashKey 都是 String 类型的。在这种情况下他们都只需要使用RedisSerializer.string()进行序列化和反序列化这个序列化器实际上就是将字符串按照 UTF-8 编码转换为字节或者相反。对于 Value 和 HashValue这里使用 Jackson 将其转换为 JSON 字符串或者相反。
因为这里需要使用 Jackson所以需要添加相应的依赖
dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.16.0/version
/dependency一般的 Spring 项目不需要额外引入因为 spring-mvc 默认包含 Jackson 依赖。 重新编写测试用例使用类型参数
SpringBootTest
class SpringDataRedisDemoApplicationTests {Autowiredprivate RedisTemplateString, Object redisTemplate;Testvoid testString() {ValueOperationsString, Object ops redisTemplate.opsForValue();ops.set(name, 王二);String val (String) ops.get(name);System.out.println(val);ops.set(user:2, new User(Jack, 18));User user (User) ops.get(user:2);System.out.println(user);}
}这里使用了一个自定义的 POJO 类
Data
NoArgsConstructor
AllArgsConstructor
public class User {private String name;private Integer age;
}需要添加 Lombok 依赖。 现在在 Redis 服务器上就能看到 Key 为user:2值为 JSON 串的键值对
{class: cn.icexmoon.springdataredisdemo.pojo.User,name: Jack,age: 18
}可以看到Value 中包含类的完整包名这也是为什么可以反序列化出具体类型的对象。
StringRedisTemplate
上面的方案虽然可以很好的解决序列化和反序列化的问题但有一个缺点Value 中包含完整类名占用 Redis 的存储空间。如果不希望 Redis 的 Value 中包含完整类名占用额外空间就需要手动序列化和反序列化这样我们只需要向 RedisTemplate 中传入 String 类型的 Key 和 Value此时我们可以使用一个更简单的类型——StringRedisTemplate
public class Tests2 {Autowiredprivate StringRedisTemplate redisTemplate;private static final ObjectMapper mapper new ObjectMapper();Testpublic void test() throws JsonProcessingException {ValueOperationsString, String ops redisTemplate.opsForValue();User user new User(Jack, 18);String jsonUser mapper.writeValueAsString(user);ops.set(user:3, jsonUser);jsonUser ops.get(user:3);user mapper.readValue(jsonUser, User.class);System.out.println(user);}
}此时user:3中的 Value
{name: Jack,age: 18
}本文的完整示例代码可以从这里获取。
参考资料
黑马程序员Redis入门到实战教程