学习来源:B站黑马程序员免费视频

Redis的一些Java客户端

客户端特点
Jedis以Redis命令作为方法名称,学习成本低,简单实用。但是Jedis实例是线程不安全的,多线程环境下需要基于连搜池来使用
LettuceLettuce是基于Netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持Redis的哨兵模式、集群模式和管道模式。
RedissonRedisson是一个基于Redis实现的分布式、可伸缩的Java数据结构集合,包含了诺如Map,0ueue、LockSemaphore、AtomicLong等强大功能
Spring Data Redis兼容Jedis和Lettuce

Jedis的Github网址

Jedis的官网地址:https://github.com/redis/jedis

Jedis学习-配置并测试能否运行Jedis简单代码

1.先创建一个maven项目,在其pom文件引入如下依赖:

<!––jedis––>
<dependency>
<groupId>redis.clients </groupId>
<artifactId>jedis </artifactId>
<version>5.2.0 </version>
</dependency>

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

2.创造一个Test_Jedis测试类来测试。

public class Test_Jedis {
    private Jedis jedis;

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

    @Test
    void teststring_set_get(){
        //存储数据
        String result = jedis.set("name","hello");
        System.out.println("result = " + result);
        //获取数据
        String name = jedis.get("name");
        System.out.println("name = " + name);
    }

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

3.测试输出:

result = OK
name = hello

测试成功!!!

由此可知,Jedis的使用步骤为:

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

Jedis学习-Jedis连接池

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

创建一个连接池类的简单代码:

public class JedisConnectionFactory {
    private static final JedisPool jedisPool;

    static {
        //配置连接池
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(8);//最大连接数
        config.setMaxIdle(8);//最大空闲连接数
        config.setMinIdle(0);//最小空闲连接数
        config.setMaxWait(Duration.ofSeconds(1));// 最大等待时间
        //创建连接池对象
        jedisPool = new JedisPool(config,
                "127.0.0.1", 6379, 1000, "123456");
    }

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

可以看出创建Jedis大致步骤为:

  • 创建一个自定义Jedis连接池类JedisConnectionFactory
  • 在类中通过静态代码块创建Jedis连接池配置对象
  • 为Jedis配置对象设置相应配置属性
  • 用Jedis配置对象和其他参数(如:Redis的IP地址和端口、连接超时时长、密码)创建Jedis连接池对象
  • 在类中创建一个静态方法返回Jedis连接池资源

接下来我们来测试一下,我们的JedisConnectionFactory类是否能正常工作。

直接在刚刚的Test_Jedis测试类中,将new 一个Jedis对象变成通过JedisConnectionFactory类返回连接池资源即可,具体代码如下。

//1.建立连接
jedis = new Jedis("localhost", 6379);
jedis = JedisConnectionFactory.getJedis();

测试输出:


SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
result = OK
name = hello

可以看到输出结果是正确的,不过却出现了一个问题——有几段红字出现,询问Chatpgt得知原因如下:

这个错误信息来自 SLF4J(Simple Logging Facade for Java),它提示在使用 SLF4J 时未找到 org.slf4j.impl.StaticLoggerBinder 类,因此无法初始化实际的日志实现。这通常表示没有引入具体的日志实现库,因此 SLF4J 默认使用空操作(NOP)记录器,导致日志输出不起作用。

看起来是我们缺少依赖导致的,解决起来也很简单,在pom文件添加一下相关依赖即可,我的代码如下:

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.12</version>
</dependency>

接下来测试一下输出:

result = OK
name = hello

可见红字没有了,问题解决!!!

一点小提醒

Jedis对象的close方法的内部代码如下:

public void close() {
    if (this.dataSource != null) {
	Pool pool = this.dataSource;
	this.dataSource = null;
	if (this.isBroken()) {
		pool.returnBrokenResource(this);
	} else {
		pool.returnResource(this);
	}
    } else {
	this.connection.close();
    }
}

可以看到当我们使用Jedis连接池时,close方法内部是归还连接池资源而不是关闭连接。

SpringDataRedis学习-了解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实现

SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:

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

SpringDataRedis学习-配置和简单使用

SpringDataRedis的简单使用步骤:

1.引入spring-boot-starter-data-redis和commons-pool2依赖。

<!--spring-boot-starter-data-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.properties文件配置Redis信息。

#主机
spring.data.redis.host=127.0.0.1
#端口
spring.data.redis.port=6379
#密码
spring.data.redis.password=123456
#选择数据库0
spring.data.redis.database=0
#启用lettuce连接池
spring.data.redis.lettuce.pool.enabled=true
#连接池最大连接数量
spring.data.redis.lettuce.pool.max-active=8
#连接池最大空闲连接数量
spring.data.redis.lettuce.pool.max-idle=8
#连接池最小空闲连接数量
spring.data.redis.lettuce.pool.min-idle=0
#连接池连接等待时间
spring.data.redis.lettuce.pool.max-wait=100ms

3.注入RedisTemplate并使用,这里只是测试一下。

@SpringBootTest
class SpringDataRedisApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

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

输出结果:


Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap
classpath has been appended
name = 虎哥

测试成功!但是这里又有红色信息出现,翻译过来大概是Java HotSpot(TM)64位服务器虚拟机警告:共享仅支持引导加载程序类,因为已附加引导程序类路径。稍微查了一下,具体解决方案可以参考如下网址。

https://www.didispace.com/article/richang/20231014-VM_warning_Sharing.html

实测里面第一个方法有效。

SpringDataRedis学习-SpringDataRedis的序列化方式

RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化。我们可以在Redis数据库里查到我们的插入的(键, 值)不是(name, 虎哥),而是(\xac\xed\x00\x05t\x00\x04name, \xAC\xED\x00\x05t\x00\x06\xE8\x99\x8E\xE5\x93\xA5)。

虽然,这对于我们通过SpringDataRedis来查询和修改这对键值对没有影响,但是有两个缺点:

  • 可读性差
  • 内存占用大

为了解决这样的问题,我们就需要自定义RedisTemplate的序列化方式,具体代码如下。

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
        //创建RedisTemplate对象
        RedisTemplate template = new RedisTemplate<>();
        //设置连接工厂
        template.setConnectionFactory(connectionFactory);
        //创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置Key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        //设置Value的序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        //返回
        return template;
    }
}

这段代码的目的是为了通过@Bean 注解将我们自定义了序列化方式的RedisTemplate对象注册到 Spring 容器中,方便在应用的各个部分使用该对象。

如何使用呢?很简单,只需要使用@Autowired注解即可。以刚刚展示的SpringDataRedis的简单使用步骤中第3步的代码为例,将@Autowired注解下面的RedisTemplate后面加个<String, Object>,这里的redisTemplate就会变成我们自定义序列化方式了的对象。

@Autowired
private RedisTemplate redisTemplate;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

输出结果:

name = 虎哥

成功!!!

小提一下,因为我们自定义的RedisTemplate对象支持<String, Object>类型的键值对,所以Value可以是一个类对象。

运行时可能出现的两种报错及解决方法:

  1. 如果错误提示找不到我们注册的Bean,大概率是因为我们定义的RedisConfig类,不在主函数类的同目录或者子目录下。
  2. 如果错误是com.fasterxml.jackson.core.JsonProcessingException,那大概率是因为没有在pom文件引入Jackson的相关依赖。

引入Jackson依赖代码如下:

<!––Jackson––>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson–databind</artifactId>
</dependency>

不过虽然我们使用Json序列化器能支持Object类型的Value,但它为了能实现自动反序列化,当我们传一个类对象为Value的话,它会私自给这个Value加了一条类名数据。

例如传的是一个User类的Value,User类定义如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
    private Integer age;
}

但是实际存储在Redis中的是Value值如下:

{
  "@class": "com.liu.redis.springdataredis.pojo.User",
  "name": "虎哥",
  "age": 18
}

可见多了一条数据:”@class”: “com.liu.redis.springdataredis.pojo.User”。

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

具体可以按照如下测试代码:

@SpringBootTest
class SpringDataRedisApplicationTests {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void testString() throws JsonProcessingException {
        //创建对象
        User user = new User("虎哥", 18);
        //手动序列化
        String json = objectMapper.writeValueAsString(user);
        //写入数据
        stringRedisTemplate.opsForValue().set("user:100", json);
        //获取数据
        json = stringRedisTemplate.opsForValue().get("user:100");
        //手动反序列化
        User u = objectMapper.readValue(json, User.class);
        //打印数据
        System.out.println("u = " + u);
    }
}

这时实际存储在Redis中的是Value值如下:

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

相对来说就不那么浪费内存了。

By Liu

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注