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

image-20221218092911191

打印结果:redis-01-value

当然还需要进行资源释放:jedis.close();

接下来在 linux 中的 redis 客户端查看是否有对应的值:

image-20221218093014580

单机版使用 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();
}

image-20221218093715247

redis 客户端结果:

image-20221218093743084

访问 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"));
}

image-20221218094701049

查看 redis 客户端结果:

image-20221218094819882

注意:JedisClusterJedis 可能有些许不同,这里不是说使用方法,而是 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 客户端查看数据:

image-20221218105256528

可以看到值是存进去了,但是前面有一串超乎预期的字符串,这就是接下来要解决的问题序列化。

再设置一个 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"));
    }

客户端结果:

image-20221218180726458

在 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;
}

image-20221218181422175

查看redis客户端:

image-20221218181943797

这样看起来就非常的符合我们预期中的结果了。

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 客户端:

image-20221219093848111

可以看到跟之前使用序列化器序列化 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 客户端:

image-20221219095732488

这里使用的序列化器: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 客户端:

image-20221219100749955

以及后面的 set 和 zset 操作都是类似的。

小结

就 redis 的学习和简单使用而言就到这儿,对于 redis 的高级使用和面试,应该要进一步学习 Java Web 知识,甚至需要到分布式和微服务才能有深刻体会。

加上学习JedisSpringDataRedis,两个和 Java 相关的,以及之间的高级操作后续学习。

posted @ 2022-12-19 10:33  CN_DADA  阅读(37)  评论(0编辑  收藏  举报