Redis 的 Java 客户端

本文介绍了Redis的Java客户端Jedis,包括其基本使用步骤、测试方法和连接池的使用。同时,文章探讨了SpringDataRedis,详细讲解了RedisTemplate工具类、序列化问题以及对Hash类型的操作。针对Jedis的线程不安全性和性能损耗,建议使用连接池。对于数据序列化,文章提到了JDK序列化的缺点并提出了解决方案。
摘要由CSDN通过智能技术生成
  • Jedis
    • 优点:以 Redis 命令作为方法名称,学习成本低廉,简单且实用
    • 缺点:Jedis 的实例是线程不安全的,在多线程的环境下需要基于线程池来使用
  • lettuce(spring 官方默认)
    • 基于 Netty 实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持 Redis 的哨兵模式、集群模式、管道模式
  • Redisson(适用于分布式的环境)
    • 基于 Redis 实现的分布式、可伸缩的 Java 数据结构的集合。包含 Map、Queue、Lock、Semaphore、AtomicLong等强大的功能

Jedis

Jedis 基本使用步骤

  1. 引入依赖
  2. 创建Jedis对象,建立连接
  3. 使用Jedis,方法名与Redis命令一致
  4. 释放资源

测试 Jedis 相关方法

如果 @BeforeEach报错,记得在 pom 文件里面引入 Junit API 包的依赖

<!-- junit-jupiter-api -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>

(这里以 String 和 Hash 两种类型为例)

package com.lcha.test;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;

import java.util.Map;

public class JedisTest {

    private Jedis jedis;

    @BeforeEach
    void setUp(){
        //1.建立连接
        jedis = new Jedis("xxxxxxxxxx",6379);
        //2.设置密码
        jedis.auth("xxxxxxxxx");
        //3.选择库
        jedis.select(0);
    }

    @Test
    void testStr(){
        //4.存入数据
        String result = jedis.set("name", "胡歌");
        System.out.println("result = " + result);
        //5.获取数据
        String name = jedis.get("name");
        System.out.println("name = " + name);
    }

    @Test
    void testHash(){
        jedis.hset("user:1","name","Jack");
        jedis.hset("user:1","age","21");

        Map<String, String> map = jedis.hgetAll("user:1");
        System.out.println(map);
    }

    @AfterEach
    void tearDown(){
        //6.释放连接
        if(jedis != null){
            jedis.close();
        }
    }
}

Jedis连接池

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。

首先创建一个 Jedis 连接池工具类

package com.lcha.jedis.util;

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

public class JedisConnectionFactory {
    private static final JedisPool jedisPool;

    static {
        //配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(8);  //最大连接数:8
        poolConfig.setMaxIdle(8);   //最大空闲连接
        poolConfig.setMinIdle(0);
        poolConfig.setMaxWaitMillis(1000);
        //创建连接池对象
        jedisPool = new JedisPool(poolConfig,"xxxx",6379,
                1000,"xxxx");
    }

    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

更改之前 Jedis 的连接方式,采用连接池连接的方式

package com.lcha.test;

import com.lcha.jedis.util.JedisConnectionFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;

import java.util.Map;

public class JedisTest {

    private Jedis jedis;

    @BeforeEach
    void setUp(){
        //1.建立连接
        //jedis = new Jedis("xxxx",6379);
        jedis = JedisConnectionFactory.getJedis();
        //2.设置密码
        jedis.auth("xxxx");
        //3.选择库
        jedis.select(0);
    }

    @Test
    void testStr(){
        //4.存入数据
        String result = jedis.set("name", "胡歌");
        System.out.println("result = " + result);
        //5.获取数据
        String name = jedis.get("name");
        System.out.println("name = " + name);
    }

    @Test
    void testHash(){
        jedis.hset("user:1","name","Jack");
        jedis.hset("user:1","age","21");

        Map<String, String> map = jedis.hgetAll("user:1");
        System.out.println(map);
    }

    @AfterEach
    void tearDown(){
        if(jedis != null){
            jedis.close();
        }
    }
}

注意:当使用连接池连接时,代码最后的 if(jedis != null){jedis.close();}不会真正的销毁连接,而是将本连接归还到连接池中

源码如下:

public void close() {
        if (this.dataSource != null) {
            Pool<Jedis> pool = this.dataSource;
            this.dataSource = null;
            if (this.isBroken()) {
                pool.returnBrokenResource(this);
            } else {
                pool.returnResource(this); //注意这里!!!!
            }
        } else {
            this.connection.close();
        }

    }

SpringDataRedis

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis

官网地址:Spring Data Redis

  • 提供了对不同Redis客户端的整合(Lettuce和Jedis)
  • 提供了RedisTemplate统一API来操作Redis
  • 支持Redis的发布订阅模型
  • 支持Redis哨兵和Redis集群
  • 支持基于Lettuce的响应式编程
  • 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
  • 支持基于Redis的JDKCollection实现

RedisTemplate 工具类

API返回值类型说明
RedisTemplate.opsForValue()ValueOperations操作 String 类型数据
RedisTemplate.opsForHash()HashOperations操作 Hash 类型数据
RedisTemplate.opsForList()ListOperations操作 List 类型数据
RedisTemplate.opsForSet()SetOperations操作 Set 类型数据
RedisTemplate.opsForZSet()ZSetOperations操作 SortedSort 类型数据
RedisTemplate通用命令

使用步骤

  1. 引入 spring-boot-starter-data-redis 依赖

    <!-- 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>
    
  2. 在 application.yml 文件中配置 Redis 信息

    spring:
      redis:
        host: xxxx
        port: 6379
        password: xxxx
        lettuce:
          pool:
            max-active: 8
            max-idle: 8
            min-idle: 0
            max-wait: 100ms
    
  3. 注入 RedisTemplate 并使用

package com.lcha;

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 RedisDemoApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void testString() {
        //写入一条String数据
        redisTemplate.opsForValue().set("name", "胡歌");
        //获取string数据
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
    }

}

序列化问题

RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的:

缺点:

  1. 可读性差
  2. 内存占用较大

解决方法:改变序列化器

自定义 RedisTemplate 序列化方式

package com.lcha.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;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@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 的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        //设置 Value 的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        //返回
        return template;
    }
}

重新运行刚才的代码,结果如下图所示:

存储对象数据时也是一样的

  1. 创建一个对象类

    package com.lcha.redis.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private String name;
        private Integer age;
    }
    
  2. 编写测试方法

  3. 	@Test
        void testSaveUser(){
            redisTemplate.opsForValue().set("user:100", new User("胡歌",21));
            User o = (User) redisTemplate.opsForValue().get("user:100");
            System.out.println("o = " + o);
        }
    
  4. 打印结果

JSON方式依然存在的缺陷

尽管 JSON 的序列化方式可以满足我们的需求,但是依然存在一些问题。

为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销。

如何解决

为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。

  1. 直接使用 StringRedisTemplate 即可

    package com.lcha;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.lcha.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.RedisTemplate;
    import org.springframework.data.redis.core.StringRedisTemplate;
    
    @SpringBootTest
    class RedisStringTests {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        void testString() {
            //写入一条String数据
            stringRedisTemplate.opsForValue().set("name", "胡歌");
            //获取string数据
            Object name = stringRedisTemplate.opsForValue().get("name");
            System.out.println("name = " + name);
        }
    
        private static final ObjectMapper mapper = new ObjectMapper();
    
        @Test
        void testSaveUser() throws JsonProcessingException {
            //创建对象
            User user = new User("虎哥",21);
            //手动序列化
            String json = mapper.writeValueAsString(user);
            //写入数据
            stringRedisTemplate.opsForValue().set("user:200", json);
            //获取数据
            String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
            User user1 = mapper.readValue(jsonUser, User.class);
            System.out.println("user1 = " + user1);
        }
    
    }
    
  2. 结果如下

对 Hash 类型的操作

  1. 编写方法

    @Test
        void testHash(){
            stringRedisTemplate.opsForHash().put("user:300", "name", "张三");
            stringRedisTemplate.opsForHash().put("user:300", "age", "18");
    
            Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:300");
            System.out.println("entries = " + entries);
    
        }
    
  2. 结果如下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值