引用学习:https://space.bilibili.com/95256449/

Jedis连接工具

什么是Jedis?

它是官方推荐的Java连接开发工具!使用Java操作 Redis中间件!如果你使用java操作 Redis ,那么要对 jedis 十分的熟悉!

测试

  • 在本地主机进行测试

1、打开 Redis 服务

Jedis连接工具 和 SpringBoot整合Redis

2、新建maven项目,导入依赖

<!--导入jedis的包-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.66</version>
</dependency>

3、测试连接

package com.zxh;

import redis.clients.jedis.Jedis;

public class TestPing {
    public static void main(String[] args) {
        // 1、 new Jedis 对象即可
         Jedis jedis = new Jedis("127.0.0.1",6379);
        // jedis 所有的命令就是我们之前学习的所有指令!所以之前的指令学习很重要!
        System.out.println(jedis.ping()); }
}

输出:

Jedis连接工具 和 SpringBoot整合Redis

常用API

所有的api命令,就是我们对应的上面学习的指令,一个都没有变化!

事务

package com.zxh;

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestTx {
    public static void main(String[] args) {
        // 建立连接
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        jedis.flushDB();    // 清除当前数据库

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username", "zxh");
        jsonObject.put("age", 20);
        String result = jsonObject.toJSONString();

        // 开启事务
        Transaction multi = jedis.multi();
        try {
            // 命令入队
            multi.set("user1", result);
            multi.set("user2", result);
            int i = 1/0;    // 制造异常
            //执行事务
            multi.exec();
        } catch (Exception e) { // 抛出异常,取消事务
            multi.discard();    //消事务
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();  // 关闭连接
        }

    }
}

结果:

Jedis连接工具 和 SpringBoot整合Redis

SpringBoot整合

准备工作和源码分析

简介

SpringBoot 操作数据:spring-data => jpa jdbc mongodb redis

SpringData 也是和 SpringBoot齐名的项目!

准备工作

1、创建SpringBoot项目

  • 勾选支持项

Jedis连接工具 和 SpringBoot整合Redis

Jedis连接工具 和 SpringBoot整合Redis

2、依赖

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

源码分析

进入 Redis 的依赖可以找到,底层使用的就是 Jedis,不过可以看到还有另外一个 lettuce

Jedis连接工具 和 SpringBoot整合Redis

说明

Jedis:采用的是直连,所以在多个线程操作的话,是不安全的!如果想要避免不安全,使用 Jedis pool 连接池! 更像 BIO(Blocking I/O,阻塞IO)

lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据量,更像 NIO(Nonblocking I/O,非阻塞IO)

 NIO和BIO解释

Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高

两张图让你了解BIO和NIO的区别:

                阻塞IO

Jedis连接工具 和 SpringBoot整合Redis

                非阻塞IO

Jedis连接工具 和 SpringBoot整合Redis

这两张图可以看出,NIO的单线程能处理连接的数量比BIO要高出很多,而为什么单线程能处理更多的连接呢?原因就是图二中出现的Selector

当一个连接建立之后,它有两个步骤要做:

  • 第一步是接收客户端发过来的全部数据

  • 第二步是服务端处理完请求之后返回response客户端

NIO和BIO的区别主要是在第一步:

  • 在BIO中,等待客户端发数据这个过程是阻塞的,这样就造成了一个线程只能处理一个请求的情况,而机器能支持的最大线程数是有限的,这就是为什么BIO不能支持高并发的原因。

  • 而NIO中,当一个Socket 建立之后,Thread 并不会阻塞接收这个 Socket,而是将这个请求交给 Selector,Selector 会不断的去遍历所有的 Socket,一旦有一个Socket 建立完成,他会通知Thread,然后 Thread 处理完数据后再返回给客户端——这个过程是不阻塞的,这样就能让一个 Thread 处理更多的请求了。

RedisAutoConfiguration自动配置源码分析

1、在SpringBoot的自动配置文件中,搜索redis可以找到对应的 Redis自动配置类

Jedis连接工具 和 SpringBoot整合Redis

 Jedis连接工具 和 SpringBoot整合Redis

 2、RedisAutoConfiguration类的源码

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
// 对应的引用配置文件 RedisProperties.class,所以说redis所有的配置可以在这里查看
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    
       // 默认的是 RedisTemplate,他没有过多的设置,其中的 Redis对象都是还需要序列化的!
    // 两个泛型都是Object类型,我们之后使用需要强转 <String, Object>
    // 所以@ConditionalOnMissingBean 表示我们可以自定义 RedisTemplate 来使用,那样会更加方便
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    // StringRedisTemplate,由于string类型是reids中最常用的类型,所以单独提出来,方便开发人员调用
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

}

配置文件

  • 可以在配置文件 以 spring.redis.xxx 来进行配置

Jedis连接工具 和 SpringBoot整合Redis

 两个API使用哪个?

  • 哪个好已经在上面说过了,lettuce支持高并发

  • 可以看到 源码中,两个方法都传入了RedisConnectionFactory对象

public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)

进入这个接口可以看到有两个实现类

Jedis连接工具 和 SpringBoot整合Redis

 可以看到 JedisConnectionFactory中有很多类没有生效

Jedis连接工具 和 SpringBoot整合Redis

 可以看到 LettuceConnectionFactory是生效的

Jedis连接工具 和 SpringBoot整合Redis

 所以在配置文件中配置时需要注意:SpringBoot 2.x以后 默认使用的lettuce,不要傻傻的配置到jedis了

Jedis连接工具 和 SpringBoot整合Redis

测试

1、导入依赖

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置文件

# 配置 redis,不过默认配置就是本机,端口也是6379,这里为了看的清除
spring.redis.host=127.0.0.1
spring.redis.port=6379

3、测试连接

  • 在测试类中进行测试,可以直接注入 RedisTemplate 对象

package com.zxh;

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.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        System.out.println(connection.ping());
    }

}

结果:

Jedis连接工具 和 SpringBoot整合Redis

4、API测试

package com.zxh;

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.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        /*
            redisTemplate:操作不同的数据类型,api和之前的命令都是一样的
            opsForValue():操作字符串,类型String
            opsForSet
            opsForHash
            opsForZSet
            opsForGeo
            opsForHyperLogLog
         */
        // 除了进本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的 CRUD

        // 获取redis的连接对象
        // RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        // RedisConnection connection = connectionFactory.getConnection();
        // System.out.println(connection.ping());

        redisTemplate.opsForValue().set("name", "zxh");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

}

结果:Jedis连接工具 和 SpringBoot整合Redis

自定义RestTemplate

为什么要自定义?

  • 首先RestTemplate 它没有过多的配置

  • 默认的RestTemplate 使用的是jdk序列化,会有一些问题

关于序列化

  • 我们基于上面的测试,试一下存入的是中文的情况

1、测试

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("name", "愤怒的张迅豪");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

}

结果:Jedis连接工具 和 SpringBoot整合Redis

 2、使用客户端连接,发现已经被转义过的Jedis连接工具 和 SpringBoot整合Redis

源码分析的时候就说过了,所有的reids对象都需要进行序列化,而我们默认使用的RestTemplate的序列化,所以说是有问题的

3、源码分析

我们可以看到源码中,创建了默认的RedisTemplate

Jedis连接工具 和 SpringBoot整合Redis

点进去可以看到,序列化设置都是nullJedis连接工具 和 SpringBoot整合Redis

 在构造器中,可以看到默认的序列化方式Jedis连接工具 和 SpringBoot整合Redis

自定义的RestTemplate

  • 开发通用模板,在公司拿过来直接用即可!

  • 还需要导入jackson依赖

<!-- jackson-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.2</version>
</dependency>

RedisConfig配置类

package com.zxh.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration  // 配置类
public class RedisConfig {

    // 自定义RedisTemplate
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        // 为了开发的方便,一般直接使用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory); // redis连接的线程安全工厂,源码也是这样配置的

        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //解决jackson2无法序列化LocalDateTime的问题,这里扩展一个LocalDateTime类型,它是日期类型对象 jdk1.8出的(并且这个类是不可变的和线程安全的,可以研究一下它的API),当然还需要对这个对象进行json格式的转换,如下图:
        om.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
        om.registerModule(new JavaTimeModule());
        
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式
         template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
         template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
         template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
         template.setHashValueSerializer(jackson2JsonRedisSerializer);
         template.afterPropertiesSet();

        return template;
    }

}

如果不配合使用,LocalDateTime类型放到Redis数据库时,会变得很长一段,很难维护。Jedis连接工具 和 SpringBoot整合Redis

测试

控制台没问题

Jedis连接工具 和 SpringBoot整合Redis

 客户端连接,命令行也可以显示Jedis连接工具 和 SpringBoot整合Redis

 

 最后:在我们真实的开发中,或者你们在公司,一般都可以看到一个公司自己封装RedisUtil

 

引用学习:https://space.bilibili.com/95256449/