当前位置 : 主页 > 编程语言 > 其它开发 >

Redis的Java客户端

来源:互联网 收集:自由互联 发布时间:2022-06-03
- Jedis - 优点:以 Redis 命令作为方法名称,学习成本低廉,简单且实用 - 缺点:Jedis 的实例是线程不安全的,在多线程的环境下需要基于线程池来使用- lettuce(spring 官方默认) - 基于
- Jedis - 优点:以 Redis 命令作为方法名称,学习成本低廉,简单且实用 - 缺点:Jedis 的实例是线程不安全的,在多线程的环境下需要基于线程池来使用 - lettuce(spring 官方默认) - 基于 Netty 实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持 Redis 的哨兵模式、集群模式、管道模式 Redis 的 Java 客户端
  • 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

官网地址:https://spring.io/projects/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. 结果如下

网友评论