05-Redis的常见客户端Jedis和SpringDataRedis的使用,详解StringRedisTemplate和RedisTemplate的区别,自定义序列化方式的步骤

Redis的Java客户端

常见的客户端

Redis官网中提供了各种语言的客户端地址,其中Java客户端也包含很多(黄色五角星标记的就是推荐使用的)

  • Jedis: 以Redis命令作为方法名称简单实用但Jedis实例是线程不安全的, 多线程环境下需要基于连接池来使用
  • Lettuce: 也提供了Redis命令对应的API并且是线程安全的,且基于Netty实现支持同步/异步和响应式编程方式, 支持Redis的哨兵模式、集群模式和管道模式
  • Redisson: 在Redis基础上实现了分布式的可伸缩的java数据结构(如Map等), 支持跨进程的同步机制(Lock、Semaphore等待),适合用来实现特殊功能需求
  • SpringDataRedis: 对Jedis和Lettuce做了抽象和封装,在Spring Boot项目中还提供了对应的Starterspring-boot-starter-data-redis

在这里插入图片描述

Jedis客户端

使用Jdeis操作Redis

第一步: 创建一个Maven工程: 引入jedis依赖

<!--jedis-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.0</version>
</dependency>
<!--单元测试-->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>

第二步: 指定ip,端口号,密码创建连接Redis的连接对象Jedis

第三步: 使用连接对象提供的方法(方法名与Redis命令一致)操作Redis实现增删改查操作

第四步: 关闭连接对象,释放资源

@SpringBootTest
class RedisTestApplicationTests {
    private Jedis jedis;
    // 在每个@Test方法之前执行的方法
    @BeforeEach
    void setUp() {
        // 通过直连的方式手动创建Jedis对象
        jedis = new Jedis("192.168.150.101", 6379);
        // 从JedisPool连接池中获取Jedis对象
        jedis = JedisConnectionFactory.getJedis();
        // 输入Redis数据库的密码
        jedis.auth("123456");
        // 选择库(默认选择的是0号库)
        jedis.select(0);
    }

    // 测试String类型
    @Test
    void testString() {
        // 存入数据
        String result = jedis.set("name", "虎哥");
        System.out.println("result = " + result);
        // 获取数据
        String name = jedis.get("name");
        System.out.println("name = " + name);
    }

    // 测试Hash类型
    @Test
    void testHash() {
        // 插入hash数据
        jedis.hset("user:1", "name", "Jack");
        jedis.hset("user:1", "age", "21");

        // 获取
        Map<String, String> map = jedis.hgetAll("user:1");
        System.out.println(map);
    }
	// 在每个@Test方法之后执行的方法
    @BeforeEach
    @AfterEach
    void tearDown() {
        if (jedis != null) {
            // close方法内判断如果使用了数据库连接池是吧Jedis对象归还而不是关闭
            jedis.close();
        }
    }
}

使用Jedis连接池

Jedis本身是线程不安全的,并且频繁的创建和销毁连接对象会有性能损耗,建议从Jedis连接池中获取Jedis连接对象(这些连接对象都是提前创建的)

第一步: 配置JedisConnectionFactory的相关配置,如连接池配置、服务端ip、服务端端口、超时时间、Redis的密码

import redis.clients.jedis.*;
public class JedisConnectionFactory {
	// JedisPool是Jedis官方提供的连接池对象
    private static JedisPool jedisPool;

    static {
        // 配置JedisConnectionFactory
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        // 设置最大连接数
        poolConfig.setMaxTotal(8);
        // 设置最大空闲连接
        poolConfig.setMaxIdle(8);
        // 设置最小空闲连接
        poolConfig.setMinIdle(0);
        // 设置获取Jedis对象的最大等待时间
        poolConfig.setMaxWaitMillis(1000);
        // 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码
        jedisPool = new JedisPool(poolConfig, "192.168.150.101", 6379, 1000, "123321");
    }
	
    // 从连接池中获取Jedis连接对象(这些对象都提前创建好了)
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

第二步: 从JedisConnectionFactory连接池中获取Jedis连接对象

@SpringBootTest
class RedisTestApplicationTests {
    private Jedis jedis = JedisConnectionFactory.getJedis();
    @Test
    void testString(){
        jedis.set("name","Kyle");
        String name = jedis.get("name");
        System.out.println("name = " + name);
    }
  
    @AfterEach
    void tearDown(){
        if (jedis != null){
            jedis.close();
        }
    }
}

SpringDataRedis客户端

RedisTemplate操作redis

SpringData是Spring提供的数据操作的模块,该模块对各种数据库进行了集成,其中对Redis的集成模块就叫做SpringDataRedis(Reids的Java客户端依赖)

  • 对不同的Redis客户端Lettuce和Jedis进行了整合(默认采用Lettuce)
  • 在对Redis进行增删改查操作时提供了RedisTemplate工具类用来统一执行的API
  • 支持Redis的发布订阅模型
  • 支持Redis哨兵和Redis集群
  • 支持基于Lettuce的响应式编程
  • 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
  • 支持基于Redis的JDKCollection实现

SpringDataRedis中提供了RedisTemplate工具类, 其中封装了各种对Redis的操作的API, 并且将不同数据类型的操作API封装到了不同的返回值类型中
在这里插入图片描述

第一步: 新建一个maven项目然后引入相关依赖, SpringBoot已经提供了SpringDataRedis的stater即spring-boot-starter-data-redis

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.heima</groupId>
    <artifactId>redis-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--Redis客户端的依赖SpringDataRedis,简化Redis的操作-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--common-pool-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
</project>

第二步: 编写application.yml文件配置Redis的参数信息(IP,端口,连接池等)

spring:
  redis:
    host: 主机IP地址
    port: 6379
    password: 123456
    database: 0 #操作的是0号数据库
    # 连接池的配置(spring默认使用的就是lettuce,使用jedis需要手动设置)
    lettuce:
      pool:
        max-active: 8  #最大连接数
        max-idle: 8 #连接池中的最大空闲连接
        min-idle: 0 #连接池中的最小空闲连接
        max-wait: 100ms  #连接池最大阻塞等待时间

自定义序列化器

RedisTemplate可以接收任意Object作为值写入Redis, 只不过写入前会把Object对象序列化为字节形式, 默认是采用JDK序列化(可读性差 , 内存占用较大)

在这里插入图片描述

@SpringBootTest
class RedisStringTests {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void testString() {
        // 此时name和"虎哥"都当成Object然后采用JDK序列化的方式以字节的形式存入Reids
        redisTemplate.opsForValue().set("name", "虎哥");
        // 查询String数据时会反序列化输出到控制台
        Object name = stringRedisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);// name = 虎哥
    }
}

config包下编写对应的配置类RedisConfig将RedisTemplate的序列化器由默认的JDK序列化方式换成JSON序列化方式

  • key或者hashkey一般情况下都是字符串,所以可以采用StringRedisSerializer的序列化方式
  • value或者hashvalue一般情况下可能是对象采用GenericJackson2JsonRedisSerializer的序列化方式所以需要引入Jackson的依赖
<!--Jackson依赖-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        // 创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置连接工厂
        template.setConnectionFactory(connectionFactory);
        // 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // 设置Key和HashKey的序列化方式
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        //redisTemplate.setKeySerializer(new StringRedisSerializer());
        //redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        
        // 设置Value和HashValue的序列化方式
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        // 最终返回对象将其注册到容器中
        return template;
    }
}

测试GenericJackson2JsonRedisSerializer序列化工具

  • 优点: 可以将Java对象自动的序列化为JSON字符串存入Redis,查询时又会自动把JSON反序列化为Java对象,提升可读性
  • 缺点:得到的JSON字符串记录了序列化时对应的class名称(目的是为了查询时实现自动反序列化), 但这也会带来额外的内存开销

在这里插入图片描述

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private Integer age;
}
@Test
void stringTest(){
    redisTemplate.opsForValue().set("user:100",new User("虎哥",21));
}

StringRedisTemplate

我们可以不使用JSON序列化器来处理value而是统一使用StringRedisSerializer的序列化方式处理value

  • 优点: 节省内存空间
  • 缺点: 要求只能存储String类型的key和value, 当需要存储Java对象时就要手动完成对象的序列化和反序列化(这样class信息就不会写入Redis)

SpringDataRedis就提供了RedisTemplate的子类StringRedisTemplate, 它的key和value的序列化方式默认是StringRedisSerializer,省去了自定义序列化方式的步骤

public class StringRedisTemplate extends RedisTemplate<String, String> {
    public StringRedisTemplate() {
        this.setKeySerializer(RedisSerializer.string());
        this.setValueSerializer(RedisSerializer.string());
        this.setHashKeySerializer(RedisSerializer.string());
        this.setHashValueSerializer(RedisSerializer.string());
    }

在这里插入图片描述

@SpringBootTest
class RedisStringRedisTemplateTests {
    // StringRedisTemplate的key和value的序列化方式默认就是StringRedisSerializer
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    // SpringMvc默认使用的JSON序列化工具,也可以使用fastjson
    private static final ObjectMapper mapper = new ObjectMapper();

    @Test
    void testSaveUser() throws JsonProcessingException {
        // 创建对象
        User user = new User("虎哥", 21);
        // 手动序列化为json字符串
        String json = mapper.writeValueAsString(user);
        // 将json字符串写入Redis
        stringRedisTemplate.opsForValue().set("user:200", json);
        // 获取数据得到的也是json字符串
        String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
        // 手动将json字符串反序列化为java对象
        User user1 = mapper.readValue(jsonUser, User.class);
        System.out.println("user1 = " + user1);
    }
}

最终存入Redis中的数据

{
  "name": "虎哥",
  "age": 21
}

操作其他数据类型

String类型数据操作(自定义序列化器或注入StringRedisTemplate): redisTemplate.opsForValue()

方法名功能
void set(Stirng key,String value)向数据库中存入一个键值对
set(key,value, 超时时间, 时间单位)设置键值对的有效时间,时间单位如TimeUnit.SECONDS/MINUTES
boolean setIfAbsent()当key不存在的时候才会添键值对,如果key存在不执行任何操作并返回false
String get(String value)获取数据库中某个key对应的value
@Test
void stringTest() {
    //获取对象
    ValueOperations valueOperations = redisTemplate.opsForValue();
    //设置name为Hades
    valueOperations.set("name","Hades");
    String name = (String) valueOperations.get("name"); 
    //Hades
    System.out.println(name);
    //设置age为9527且有效时间10秒
    valueOperations.set("age", "9527", 10, TimeUnit.SECONDS);
    String age = (String) valueOperations.get("age");
    //9527,10秒过后再去redis中获取name的值则输出`nil`表示不存在
    System.out.println(age);
    //如果不存在则设置name为Kyle,由于name已经存在所以最后返回false
    Boolean aBoolean = valueOperations.setIfAbsent("name", "Kyle");
    System.out.println(aBoolean);
}

Hash类型数据操作: redisTemplate.opsForHash()

方法名功能
void put(Stirng key,String failed,Sting value)向数据库中存入一个key,值是字段名和字段值
Object get(String key,Sting failed)获取数据库中某个key中某个字段的字段值
Set< String> keys(String key)获取数据库中某个key下的所有字段
List< String> keys(String key)获取数据库中某个key下的所有字段值
Map<String,String> entries(String key)获取数据库中某个key下所有的字段及其字段值,返回值是个Map集合
@Test
void hashTest() {
    // 存值
    HashOperations hashOperations = redisTemplate.opsForHash();
    hashOperations.put("100", "name", "Hades");
    hashOperations.put("100", "age", "18");
    hashOperations.put("100", "hobby", "Apex");
    // 获取某个key中name字段的值
    hashOperations.get("100","name");
    // 获取hash结构中某个key下所有的字段和字段值,返回值是个Mapj集合
    Map<String, String> map = hashOperations.entries("100");
    // 获取Map集合中所有的key(字段名)
    Set<String> keySet = map.keySet();
    for (String hashKey : keySet) {
        // 获取集合中具体字段对应的字段值
        System.out.println(hashKey + ":" + map.get(hashKey));
    }
    System.out.println("====================");
    // 获取hash结构中某个key下所有的字段
    Set<String> keys = hashOperations.keys("100");
    for (String key : keys) {
        System.out.println(key);
    }
    System.out.println("====================");
    
    //获取hash结构中某个key所有的字段值
    List<String> values = hashOperations.values("100");
    for (String value : values) {
        System.out.println(value);
    }
}

List类型数据操作: redisTemplate.opsForList()

方法名功能
void leftPush(String key,String element)从某个key链表的左侧插入一个元素
void leftPushAll(String key,String element…)从某个key链表的左侧插入多个元素
List< String> range(String key, start, stop)获取某个key链表中指定下标范围内的元素
Long size(String key)获取某个key链表的长度(用于遍历)
Object leftPop(String key)从左侧开始弹出某个链表的第一个元素,返回弹出的元素
Object rightPop(String key)从右侧开始弹出某个链表的第一个元素,返回弹出的元素
@Test
void listTest() {
    ListOperations listOperations = redisTemplate.opsForList();
    //存数据
    listOperations.leftPush("testData", "A");
    //一次存多个数据
    listOperations.leftPushAll("testData", "B", "C", "D");
    //获取某个key链表的所有元素,(0,-1)表示查询所有元素
    List<String> testDatas = listOperations.range("testData", 0, -1);
    for (String tableData : testDatas) {
        System.out.print(tableData + " ");
    }
    System.out.println();
    //获取当前list长度,用于遍历
    Long size = listOperations.size("testData");
    int value = size.intValue();
    //遍历链表并弹出链表的元素
    for (int i = 0; i < value; i++) {
        System.out.print(listOperations.leftPop("testData") + " ");
    }
    //最后输出一下当前list长度
    System.out.println(listOperations.size("testData"));
}

Set类型(无须的Set集合)数据操作: redisTemplate.opsForSet()

方法名功能
add(String key,String element…)从某个key链表的左侧插入一个或多个元素(元素不可重复,重复会覆盖)
Set< String> members(String key)获取某个key的Set集合中的所有元素
void remove(String key,String element…)删除某个key的Set集合中的一个或多个元素
@Test
void setTest() {
    SetOperations setOperations = redisTemplate.opsForSet();
    //存数据,这里存了两个a但元素会覆盖
    setOperations.add("tmp", "a", "b", "c", "d", "a");
    //遍历输出
    Set<String> tmpData = setOperations.members("tmp");
    for (String value : tmpData) {
        System.out.print(value + " ");
    }
    System.out.println("=================");
    //删除元素
    setOperations.remove("tmp", "b", "c");
    //再次遍历输出
    tmpData = setOperations.members("tmp");
    for (String value : tmpData) {
        System.out.print(value + " ");
    }
}

ZSet类型(有序的Set集合)数据操作: redisTemplate.opsForZSet()

方法名功能
add(String key,String value,double score)从某个key链表的左侧插入一个或多个元素(元素之间会根据分数排序)
Set< String> range(String key, start, stop)获取某个key的Set集合中指定下标范围内的元素
incrementScore(String key,String value,double,score)修改某个key的Set集合中某个元素的分数
Set< String> members(String key)获取某个key的Set集合中的所有元素
void remove(String key,String value…)删除某个key的Set集合中的一个或多个元素
@Test
void zsetTest() {
    ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    //存值(元素不可重复)
    zSetOperations.add("myZset", "a", 0.0);
    zSetOperations.add("myZset", "b", 1.0);
    zSetOperations.add("myZset", "c", 2.0);
    zSetOperations.add("myZset", "a", 3.0);
    //取出所有元素
    Set<String> myZset = zSetOperations.range("myZset", 0, -1);
    for (String s : myZset) {
        System.out.println(s);
    }
    //修改分数
    zSetOperations.incrementScore("myZset", "b", 4.0);
    //取出所有元素
    myZset = zSetOperations.range("myZset", 0, -1);
    for (String s : myZset) {
        System.out.println(s);
    }
    //删除成员
    zSetOperations.remove("myZset", "a", "b");
    //取出所有元素
    myZset = zSetOperations.range("myZset", 0, -1);
    for (String s : myZset) {
        System.out.println(s);
    }
}

通用的数据类型操作: redisTemplate.api

方法名功能
Set< String> keys(String 通配符)* 表示获取所有的key
boolean hasKey(String key)查看指定的key是否存在
void delete(String key)删除指定key
DateType type(String key)获取某个key的类型
@Test
void commonTest() {
    //查看所有key
    Set<String> keys = redisTemplate.keys("*");
    for (String key : keys) {
        System.out.println(key);
    }
    //查看是否存在指定key
    System.out.println(redisTemplate.hasKey("Random"));
    //删除指定key
    redisTemplate.delete("myZset");
    //输出指定key的类型
    Datetype datatype = redisTemplate.type("tmp")
    System.out.println(datetype.name);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值