Redis的Java客户端

  • 我们最早使用的Redis的Java客户端是Jedis,但是它具有线程安全问题,多线程环境下需要连接池连接。
  • Lettuce是基于Netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持Redis哨兵模式、集群模式和管道。因此Srping默认继承Lettuce
    Spring中的Spring Data Redis集成了以上两种客户端。
  • Redission是一个一个基于Redis实现的分布式、可伸缩的Java数据结构集合。

Jedis客户端

入门

@BeforeEach由junit5提供,在每个@Test方法执行前,将该注解下的代码执行一遍。
使用步骤:

  • 引入依赖
    </properties>
    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.10.1</version>
        </dependency>
    </dependencies>
  • 创建jedis对象,建立连接(需要的参数:ip、port、密码)
  • 操作数据库
  • 释放连接
package com.heima.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 Zonda
 * @version 1.0
 * @description TODO
 * @2024/7/1 10:00
 */
public class JedisTest {
    private Jedis jedis;
    @BeforeEach
    void setup(){
        jedis = new Jedis("192.168.101.65",6379);
        jedis.auth("123321");
        jedis.select(0);

    }
    @Test
    void test(){
        jedis.set("name", "zonda");
        System.out.println(jedis.get("name"));
    }
    @Test
    void testmap(){
        HashMap<String, String> map = new HashMap<>();
        map.put("zonda", "man");
        map.put("lucy", "woman");
        jedis.hset("user:1",map);
        System.out.println(map);
    }
    @AfterEach
    void close(){
        if(jedis != null){
            jedis.close();
        }
    }
}

Jedis线程池

连接池的几个主要参数:

  • 最大连接
  • 最大空闲连接
  • 最小空闲连接
  • 最长等待时间:连接池的最长等待时间通常是指在连接池中没有可用连接时,客户端尝试获取连接的最大等待时间。

Jedis本身是不安全的,并且频繁创建和销毁连接会有性能损耗,因此可以使用Jedis连接池代替Jedis。

  • 首先用工厂模式创建连接池对象
package com.heima.jedis.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author Zonda
 * @version 1.0
 * @description TODO
 * @2024/7/1 10:25
 */
public class JedisConnectionFactory {
    private static final JedisPool jedisPool;
    static{
        //连接池配置类
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        //配置最大连接数
        jedisPoolConfig.setMaxTotal(8);
        //设置最大空闲连接数
        jedisPoolConfig.setMaxIdle(8);
        //设置最小空闲连接数
        jedisPoolConfig.setMinIdle(0);
        //设置最长等待时间,这里是获取线程池的最长等待时间
        jedisPoolConfig.setMaxWaitMillis(20);

        //建立连接池
        jedisPool = new JedisPool(jedisPoolConfig,
                "192.168.101.65",
                6379,
                1000,
                "123321");//1000是建立Redis连接的最长等待时间
    }
    public static Jedis getJedisPool(){
        //调用getResource()获取Jedis对象
        return jedisPool.getResource();
    }
}
  • 将Jedis中的
jedis = new Jedis("192.168.101.65",6379);

改为

jedis = JedisConnectionFactory.getJedisPool();

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实现(如链表、队列,因为在Redis中是分布式、跨系统的)

在Jedis中,每一种方法对应着一种操作指令(如set对应jedis.set()),所以学习成本较低,但是较为臃肿
但是SpringDataRedis中提供的RedisTemplate工具类,封装了各种对于Redis的操作,将不同数据类型的操作API封装到不同类型中
在这里插入图片描述
测试SpringDataRedis:

  • 创建一个SpringBoot的项目,在依赖选择中选择SpringDataRedis,这样创建好的项目中就已经引入了该依赖。
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  • 引入common依赖,这个依赖用于线程池操作
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
  • 在application.yaml中配置Redis的信息(yaml中配置)
spring:
  redis:
    host: 192.168.101.65
    port: 6379
    password: 123321
    lettuce:
      pool:
        max-active: 8
        min-idle: 0
        max-idle: 8
        max-wait: 1000
  • 注入RedisTemplate
  • 编写测试
package org.example.springdataredisdemo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class SpringdataredisdemoApplicationTests {
    @Autowired
    RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("name", "Luka Magic");
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println(name);
    }
}

RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,因为我们是自动注入的RedisTemplate,默认是采用JDK序列化,得到的结果是这样的:
在这里插入图片描述
这样的方式有两个缺点:

  • 可读性cha
  • 内存占用较大

那么我们该如何用我们想要的序列化方式来序列化键值对呢?解决方法:不用默认的JDK序列化
其实RedisSerializer有很多实现类:

  • 一般对于肯定为字符串的。一般是key用StringRedisSerializer
  • 可能为字符串可能为对象的,一般是value用GenericJackson2JsonRedisSerializer
    在这里插入图片描述

可以通过@Configuration来声明一个我们想要的RedisTemplate类,在这个类内部定义序列化方式。由RedisTemplate源码,可以看出该类中有四个可以设置的序列化的对象,分别是key、value、hashkey、hashvalue:

    @Nullable
    private RedisSerializer keySerializer = null;
    @Nullable
    private RedisSerializer valueSerializer = null;
    @Nullable
    private RedisSerializer hashKeySerializer = null;
    @Nullable
    private RedisSerializer hashValueSerializer = null;

RedisConnectionFactory 用于创建和管理 RedisConnection 实例。在Spring Data Redis中,这是与Redis服务器通信的主要入口点,所以

  • 首先要配置依赖项(注意:要引入jackson-databind依赖):
    在这里插入图片描述
  • 再重写RedisConfig类
package com.heima.redis.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 Zonda
 * @version 1.0
 * @description TODO
 * @2024/7/1 12:31
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        //创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //配置连接工厂
        template.setConnectionFactory(connectionFactory);
        //创建JSON序列化工具
        GenericJackson2JsonRedisSerializer json = new GenericJackson2JsonRedisSerializer();
        //设置Key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        //设置Value的序列化
        template.setValueSerializer(json);
        template.setHashKeySerializer(json);
        //返回
        return template;
    }
}

  • 再改一下自动注入的redisTemplate的类型
    @Autowired
    private RedisTemplate redisTemplate;

改为:

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
  • 当然也可以传入对象,Redis可以自动将对象序列化存入,在取出的时候再反序列化

    • 先创建一个User类:
    package com.heima.redis.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * @author Zonda
     * @version 1.0
     * @description TODO
     * @2024/7/1 14:33
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private String name;
        private Integer age;
    }
    
    
    • 再测试存入对象
        @Test
        void testSaveUser(){
            redisTemplate.opsForValue().set("user:100", new User("zonda",25));
            Object o = redisTemplate.opsForValue().get("user:100");
            System.out.println(o);
        }
    
  • 在Resp客户端中查找users对象,是以JSON的形式存储的,同时还存储了类的字节码"@class": “com.heima.redis.pojo.User”,这样会占用大量的内存空间,但是要想实现自动序列化和反序列化又不得不存类的字节码文件。
    在这里插入图片描述
    但是如果存储千万级别的对象,肯定是不能存储类字节码文件的,所以我们一般都不使用JSON序列化器,而是统一使用String序列化器。当需要存储Java对象的时候,手动完成对象的序列化和反序列化。如下图所示,这样就可以解决内存占用问题了。
    在这里插入图片描述

  • 那我们是否也要像RedisTemplate<String, Object> template = new RedisTemplate<>();一样去构造一个新的RedisTemplate呢?但是不需要,因Spring提供了一个StringRedisTemplate类,它的K-V的序列化方式默认是String,省去了自定义的过程。

  • 我们可以用

    • mapper.writeValueAsString(user)手动序列化对象。
    • mapper.readValue(val,User.class)手动反序列化对象。
    package com.heima;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.heima.redis.pojo.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.core.StringRedisTemplate;
    
    /**
     * @author Zonda
     * @version 1.0
     * @description TODO
     * @2024/7/1 14:55
     */
    @SpringBootTest
    public class stringRedisTemplate {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
        @Autowired
        private static final ObjectMapper mapper = new ObjectMapper();
        @Test
        void testTemplate() throws Exception{
            User jordan = new User("Jordan", 50);
            String s = mapper.writeValueAsString(jordan);
            stringRedisTemplate.opsForValue().set("user:101",s);
            String s1 = stringRedisTemplate.opsForValue().get("user:101");
            Object o = mapper.readValue(s1, User.class);
            System.out.println((User)o);
    
        }
    
    }
    
    

打开RESP可以发现没有类的字节码了:
在这里插入图片描述

  • 总结
    在这里插入图片描述
  • RedisTemplate也可以存和取hash值,用stringRedisTemplate.opsForHash()获取操作类。
    @Test
    void testSet() throws Exception{
        stringRedisTemplate.opsForHash().put("user:102","name","Durant");
        stringRedisTemplate.opsForHash().put("user:102","age","22");
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:102");
        System.out.println(entries);
    }

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部