Redis——06 学习
Redis——06
将 Redis 的基本使用以及三种模式进行了学习和了解。接下来就学习如何在 Java 中以及 Spring Boot 框架中使用 Redis。
Jedis
Redis 在 Java 上的操作多半是集群模式。
直接创建 Maven 项目映入 Jedis 依赖即可。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
在虚拟机中运行 redis 的单机版和集群(构建集群相同的节点构建一次就可以了)。
访问单机版 Redis
打印结果:redis-01-value
当然还需要进行资源释放:jedis.close();
接下来在 linux 中的 redis 客户端查看是否有对应的值:
单机版使用 Jedis 对象来获取连接的,接下来使用连接池 Pool,Jedis 提供了连接池对象:JedisPool
// 用线程池访问 redis
public static void test02(){
// JedisPool 的配置文件对象。
JedisPoolConfig config = new JedisPoolConfig();
// 最多连接对象个数
config.setMaxTotal(20);
// 最大活动连接
config.setMaxIdle(7);
// 最小活动连接
config.setMinIdle(3);
JedisPool pool = new JedisPool(config,"192.168.12.167",6379);
// 通过 JedisPool 获取连接对象 Jedis
Jedis jedis= pool.getResource();
jedis.set("redis-02-key","redis-02-value");
System.out.println(jedis.get("redis-02-key"));
// 关闭 jedis 连接,并不需要关闭连接池 ——> JedisPool,由虚拟机关闭redis服务自动关闭。
jedis.close();
}
redis 客户端结果:
访问 Redis 集群
// 访问 redis 集群
public static void test03(){
Set<HostAndPort> nodes = new HashSet<>();
// 一共 6 个节点
nodes.add(new HostAndPort("192.168.12.167",7100));
nodes.add(new HostAndPort("192.168.12.167",7101));
nodes.add(new HostAndPort("192.168.12.167",7102));
nodes.add(new HostAndPort("192.168.12.167",7103));
nodes.add(new HostAndPort("192.168.12.167",7104));
nodes.add(new HostAndPort("192.168.12.167",7105));
// JedisCluster 默认自带 JedisPool,以及 JedisPoolConfig 也是默认的。
JedisCluster cluster = new JedisCluster(nodes);
// 通过 cluster 直接操作
cluster.set("redis-cluster-key","redis-cluster-value");
System.out.println(cluster.get("redis-cluster-key"));
}
查看 redis 客户端结果:
注意:JedisCluster
与 Jedis
可能有些许不同,这里不是说使用方法,而是 JedisCluster 中默认实现了 JedisPool,而不像 Jedis 使用 JedisPool 一样,需要 pool.getResource();
,因为这个也是比较容易理解,我们在 redis 客户端访问集群时,也是使用一个客户端,不过加了一个参数 -c
,帮我们自动的进行重定向,包括客户端连接端口切换。所以将 JedisCluster
理解成加了-c
参数的 redis 客户端,而重定向已经帮我们自动实现了。Jedis
就是没加参数的客户端。
SpringDataRedis
1. 创建 Spring Boot 项目
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
写配置文件:
application.yml
spring:
redis:
port: 6379 # 默认是6379
database: 0 # 数据库编号 默认是0
host: 192.168.12.167 # ip 地址,默认是本地 localhost
2. 使用 RedisTemplate 操作 Redis
SpringData 系列框架中,会提供 XxxTemplate
,用作数据访问客户端对象。
@SpringBootTest(classes = {RedisApp.class})
@RunWith(SpringRunner.class)
public class RedisDataTest {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Test
public void test01(){
redisTemplate.opsForValue().set("key-1","value-1");
}
}
启动运行单元测试,发现报错,大致意思是:Spring 容器中没有找到一个 RedisTemplate <String,Object>
类型将其注入。因为 SpringDataRedis
默认提供的 RedisTemplate
的泛型是:<Object,Object> 或 <String,String>
如果要使用别的泛型需要自己手动创建泛型放入容器中。
RedisConfiguration
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 此时 redisTemplate 只是一个对象,并没有 application.yml 中的相关配置参数以及默认参数
// 加载 RedisConnectionFactory 连接工厂。
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
再次运行,没有报错,登录 redis 客户端查看数据:
可以看到值是存进去了,但是前面有一串超乎预期的字符串,这就是接下来要解决的问题序列化。
再设置一个 User 类并对其进行获取:
public void test01(){
User user = new User();
user.setUsername("Tom");
user.setAge(18);
user.setClazz("1977");
user.setPassword("rootAdmin");
redisTemplate.opsForValue().set("key-1","value-1");
redisTemplate.opsForValue().set("user-1",user);
System.out.println(redisTemplate.opsForValue().get("key-1"));
System.out.println(redisTemplate.opsForValue().get("user-1"));
}
客户端结果:
在 Java 中通过键值可以直接获取到数据。
3. 序列化
Java 程序在向 Redis 存数据时,有时我们需要传递的并不是字符串类型的数据,所以 SpringDataRedis
在存储数据到 Redis 中时,都会将其进行序列化存储方便在网络中传输。
默认情况下使用的是 JDK 自带的序列化方式。最后存储到 Redis 中就如上图所示,可以看到 User 类仅仅4个简单的属性经过 JDK 的序列化后占用的空间比较大,并且可读性非常差。
所以可以使用 SpringDataRedis
提供实现比较好的序列化器,帮助我们完成序列化。
在 RedisConfiguration
中对其进行设置序列化器:
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
// 设置序列化器:
redisTemplate.setKeySerializer(new StringRedisSerializer()); // 设置 key 的序列化器
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class)); // 设置 value 的序列化器
// 设置 hash
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
return redisTemplate;
}
查看redis客户端:
这样看起来就非常的符合我们预期中的结果了。
SpringDataRedis
提供的序列化器:
那么此时在程序中来获取一下key-1、user-1
的数据类型:
class java.lang.String
class java.util.LinkedHashMap
明显 user-1 反序列化返回的引用类型是:LinkedHashMap,而我们理想的是 User,这就是我们下一个要解决的问题!反序列化时将引用类型转换成正确的类型。
4. 反序列化
为了解决 redis 存储 pojo 实例反序列化问题:
方法1:Jackson2JsonRedisSerializer
通过观察 redis 存的 User 实例对象信息,只有 User
的属性以及值。并没有更多和 User 相关的信息了,而如果此时 Admin
实例中也有其中的字段呢?程序并不能根据属性就知道是哪个实例,所以干脆就返回了 LinkedHashMap
,因为设置序列化器的泛型是 Object
,为了在存值的时候能适用所有引用类型,所以反序列化的时候我们也需要对序列化器进行设置,将泛型设置成我们需要的类即可:
@Test
public void test01(){
System.out.println(redisTemplate.opsForValue().get("key-1").getClass() + "-->" + redisTemplate.opsForValue().get("key-1"));
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(User.class)); // 反序列化时对其进行指定泛型 User
System.out.println(redisTemplate.opsForValue().get("user-1").getClass() + "-->" + redisTemplate.opsForValue().get("user-1"));
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); // 再恢复序列化的泛型 Object
}
在获取完成 pojo 实例后还需要将其 Jackson2JsonRedisSerializer
序列化器的泛型指定为 Object 防止下次还是以 User.class 的类进行序列化和反序列化。
class java.lang.String-->value-1
class com.lyl.entity.User-->User{username='Tom', age=18, password='rootAdmin', clazz='1977'}
方法2:GenericJackson2JsonRedisSerializer
可以使用 GenericJackson2JsonRedisSerializer
这个序列化器,也是 SpringDataRedis 提供的。
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 设置 hash
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
获取数据:
@Test
public void test01(){
User user = new User();
user.setUsername("Tom");
user.setAge(18);
user.setClazz("1977");
user.setPassword("rootAdmin");
redisTemplate.opsForValue().set("key-1","value-1");
redisTemplate.opsForValue().set("user-1",user);
System.out.println(redisTemplate.opsForValue().get("key-1").getClass() + "-->" + redisTemplate.opsForValue().get("key-1"));
System.out.println(redisTemplate.opsForValue().get("user-1").getClass() + "-->" + redisTemplate.opsForValue().get("user-1"));
}
结果:
class java.lang.String-->value-1
class com.lyl.entity.User-->User{username='Tom', age=18, password='rootAdmin', clazz='1977'}
查看 redis 客户端:
可以看到跟之前使用序列化器序列化 pojo 实例不同的一点是,会给其加上一个类的全限定名
,以便根据类的全限定名来对其反序列化。这样不需要像上面一样进行泛型的设置了。
同时这样做会有一点缺点:
- redis 数据中额外加入了一个 字段,如果这个类中还包含其他pojo引用,依然会再增加。
- 在反序列化时根据
@class
字段去获取类的全限定名,如果没有该字段或者项目中不存在该类,非常容易出现Class not found
异常。
两者都有自己的优缺点,根据实际情况而定。
SpringDataRedis 中提供的序列化器有这么多,一般情况下足够使用。
5. List 和 Hash
List
@Test
public void test02(){
User user = new User();
user.setUsername("Tom");
user.setAge(18);
user.setClazz("1977");
user.setPassword("rootAdmin");
redisTemplate.opsForList().leftPushAll("list",user,"list-2","list-3","list-4"); // 向左添加
redisTemplate.opsForList().rightPushAll("list","list-100","list-200","list-300","list-400"); // 向右添加
// 一般情况下 List 中存放的数据类型都应该是准确一定的,这里是测试反序列化。
List<Object> list = redisTemplate.opsForList().range("list",0,-1);
for (Object o:list){
System.out.println(o.getClass()+ "-->"+o);
}
}
结果:
class java.lang.String-->list-4
class java.lang.String-->list-3
class java.lang.String-->list-2
class com.lyl.entity.User-->User{username='Tom', age=18, password='rootAdmin', clazz='1977'}
class java.lang.String-->list-100
class java.lang.String-->list-200
class java.lang.String-->list-300
class java.lang.String-->list-400
redis 客户端:
这里使用的序列化器:GenericJackson2JsonRedisSerializer
Hash
@Test
public void test03(){
User user = new User();
user.setUsername("Tom");
user.setAge(18);
user.setClazz("1977");
user.setPassword("rootAdmin");
// 单个操作
redisTemplate.opsForHash().put("map-1","field-k1","filed-v1");
redisTemplate.opsForHash().put("map-1","field-k2","filed-v2");
// 批量操作
Map<Object,Object> map = new HashMap<>();
map.put("field-k3","v3");
map.put("field-k4","v4");
map.put("field-k5",user);
map.put("field-k6",18);
redisTemplate.opsForHash().putAll("map-1",map);
// 打印
Map<Object, Object> entries = redisTemplate.opsForHash().entries("map-1"); // 相对于 hgetall
for (Map.Entry<Object,Object> entry: entries.entrySet()){
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
}
控制台打印:
field-k3-->v3
field-k1-->filed-v1
field-k6-->18
field-k4-->v4
field-k5-->User{username='Tom', age=18, password='rootAdmin', clazz='1977'}
field-k2-->filed-v2
redis 客户端:
以及后面的 set 和 zset 操作都是类似的。
小结
就 redis 的学习和简单使用而言就到这儿,对于 redis 的高级使用和面试,应该要进一步学习 Java Web 知识,甚至需要到分布式和微服务才能有深刻体会。
加上学习Jedis
和 SpringDataRedis
,两个和 Java 相关的,以及之间的高级操作后续学习。