SpringBoot整合Redis和SpringBoot(SpringCache)整合Redis


参考博客:

https://blog.csdn.net/lingerlan510/article/details/121906813

https://blog.csdn.net/user2025/article/details/106595257

https://blog.csdn.net/Confused_/article/details/124417403

https://blog.csdn.net/weixin_56395837/article/details/121484260

 SpringBoot整合Redis

源码码云地址:https://gitee.com/zhang-zhixi/springboot-redis

一、所需依赖

这里需要注意的一点是,从在SpringBoot 2.0+后,默认的redis client是lettuce而不是一直使用的jedis,在导入依赖的时候需要再单独导入commons-pool2

关于lettuc与Jedis有什么区别?

  • lettuce: Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。
  • Jedis: Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 redis实例增加 物理连接。 这种方式更加类似于我们 BIO 一条线程连一个客户端,并且是阻塞式的,会一直连接着客户端等待客户端的命令


    org.springframework.boot
    spring-boot-starter-data-redis



    org.apache.commons
    commons-pool2



    org.springframework.boot
    spring-boot-starter-web



    com.baomidou
    mybatis-plus-boot-starter
    3.4.2


    org.projectlombok
    lombok
    true



    mysql
    mysql-connector-java
    runtime

二、Redis原理

Redis的自动配置

在SpringBoot中导入Redis的Start,我们知道会有一个xxxAutoConfiguration,这样一个类来存放我们的一些自动配置。

Redis的配置文件

点进去这个 @EnableConfigurationProperties(RedisProperties.class) ,可以看到在SringBoot下我们可以对Redis进行的一些配置:

 1 @ConfigurationProperties(prefix = "spring.redis")
 2 public class RedisProperties {
 3  
 4     /**
 5      * 可以配置使用的db下标
 6      */
 7     private int database = 0;
 8  
 9     /**
10      * 这个配置可以让我们连接到远程的redis中。例如:
11      * redis://user:password@example.com:6379
12      */
13     private String url;
14  
15     /**
16      * Redis服务端的主机名
17      */
18     private String host = "localhost";
19  
20     /**
21      * Login username of the redis server.
22      */
23     private String username;
24  
25     /**
26      * Login password of the redis server.
27      */
28     private String password;
29  
30     /**
31      * Redis的端口号
32      */
33     private int port = 6379;
34  
35     /**
36      * 是否开启安全认证
37      */
38     private boolean ssl;
39  
40     /**
41      * Read timeout.
42      */
43     private Duration timeout;
44  
45     /**
46      * Connection timeout.
47      */
48     private Duration connectTimeout;
49  
50     /**
51      * Client name to be set on connections with CLIENT SETNAME.
52      */
53     private String clientName;
54  
55     /**
56      * Type of client to use. By default, auto-detected according to the classpath.
57      */
58     private ClientType clientType;
59  
60     private Sentinel sentinel;
61  
62     private Cluster cluster;
63 }

其中主机名和端口号都有默认值,如果我们连自己的电脑,那么这两个配置都可以不用修改!我们这里不用修改配置文件,就使用默认的即可!

关于Redis的配置,仅供参考:

##########################Redis配置###################################
# 连接的那个数据库(默认为0)
spring.redis.database=0
# redis服务的ip地址(默认是本机-127.0.0.1)
spring.redis.host=182.92.209.212
# redis端口号(默认)
spring.redis.port=6379
# redis的密码,没设置过密码,可为空
spring.redis.password=123456
# 连接超时时间
spring.redis.timeout=10s
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最大连接数
spring.redis.lettuce.pool.max-active=8
# 连接池中的最大等待时间(-1表示没有限制)
spring.redis.lettuce.pool.max-wait=-1ms

Redis简单存取数据测试

实体类:User.java

/**
 * @TableName user
 */
@TableName(value = "user")
@Data
public class User implements Serializable {

    /**
     * 主键ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 姓名
     */
    @TableField(value = "name")
    private String name;

    /**
     * 年龄
     */
    @TableField(value = "age")
    private Integer age;

    /**
     * 邮箱
     */
    @TableField(value = "email")
    private String email;

    /**
     *
     */
    @TableField(value = "version")
    private Integer version;

    /**
     *
     */
    @TableField(value = "create_time")
    private LocalDateTime createTime;

    /**
     *
     */
    @TableField(value = "update_time")
    private LocalDateTime updateTime;

    /**
     *
     */
    @TableField(value = "deleted")
    private Integer deleted;

    /**
     *
     */
    @TableField(value = "create_at")
    private String createAt;

    /**
     *
     */
    @TableField(value = "password")
    private String password;

    /**
     *
     */
    @TableField(value = "update_at")
    private String updateAt;

    /**
     *
     */
    @TableField(value = "username")
    private String username;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        User other = (User) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
                && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
                && (this.getAge() == null ? other.getAge() == null : this.getAge().equals(other.getAge()))
                && (this.getEmail() == null ? other.getEmail() == null : this.getEmail().equals(other.getEmail()))
                && (this.getVersion() == null ? other.getVersion() == null : this.getVersion().equals(other.getVersion()))
                && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
                && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()))
                && (this.getDeleted() == null ? other.getDeleted() == null : this.getDeleted().equals(other.getDeleted()))
                && (this.getCreateAt() == null ? other.getCreateAt() == null : this.getCreateAt().equals(other.getCreateAt()))
                && (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword()))
                && (this.getUpdateAt() == null ? other.getUpdateAt() == null : this.getUpdateAt().equals(other.getUpdateAt()))
                && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
        result = prime * result + ((getAge() == null) ? 0 : getAge().hashCode());
        result = prime * result + ((getEmail() == null) ? 0 : getEmail().hashCode());
        result = prime * result + ((getVersion() == null) ? 0 : getVersion().hashCode());
        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
        result = prime * result + ((getDeleted() == null) ? 0 : getDeleted().hashCode());
        result = prime * result + ((getCreateAt() == null) ? 0 : getCreateAt().hashCode());
        result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode());
        result = prime * result + ((getUpdateAt() == null) ? 0 : getUpdateAt().hashCode());
        result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", name=").append(name);
        sb.append(", age=").append(age);
        sb.append(", email=").append(email);
        sb.append(", version=").append(version);
        sb.append(", createTime=").append(createTime);
        sb.append(", updateTime=").append(updateTime);
        sb.append(", deleted=").append(deleted);
        sb.append(", createAt=").append(createAt);
        sb.append(", password=").append(password);
        sb.append(", updateAt=").append(updateAt);
        sb.append(", username=").append(username);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}

测试类:SpringbootRedisApplicationTests

@SpringBootTest
class SpringbootRedisApplicationTests {
    /*操作Redis*/
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void redisTest() {
        ValueOperations redisTemplate = this.redisTemplate.opsForValue();
        User user = new User();
        user.setId(1L);
        user.setName("zhangsan");
        user.setAge(18);
        System.out.println("存入redis");
        redisTemplate.set("user", user);
        System.out.println("取出redis");
        System.out.println(redisTemplate.get("user"));
    }
}

我们来看下控制台打印的数据

 看着很正常,但是如果我们去Redis客户端中去查看数据的话,明显看到的是一些乱码的数据,虽然这些数据不影响数据的传输,但是在我们看来,这些数据确实是比较难看得懂。

 通过可视化工具来查看数据,也会发现是乱码的存在:

三、Redis的序列化

我们通常在传输数据的时候,肯定是比较想要统一格式,比如通过JSON字符串进行传输数据。

通过上面的RedisAutoConfiguration中,我们发现可以通过重写一个名为redisTemplate的Bean,就可以重新对Redis进行定制化,写个配置类,来进行重写Redis的序列化方式:

package com.zhixi.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.StringRedisSerializer;

/**
 * @ClassName RedisConfig
 * @Author zhangzhixi
 * @Description Redis配置类
 * @Date 2022-4-29 10:23
 * @Version 1.0
 */
@Configuration
public class RedisConfig {
    /**
     * @param redisConnectionFactory:配置不同的客户端,这里注入的redis连接工厂不同: JedisConnectionFactory、LettuceConnectionFactory
     * @功能描述 :配置Redis序列化,原因如下:
     * (1) StringRedisTemplate的序列化方式为字符串序列化,
     * RedisTemplate的序列化方式默为jdk序列化(实现Serializable接口)
     * (2) RedisTemplate的jdk序列化方式在Redis的客户端中为乱码,不方便查看,
     * 因此一般修改RedisTemplate的序列化为方式为JSON方式【建议使用GenericJackson2JsonRedisSerializer】
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = serializer();
        RedisTemplate redisTemplate = new RedisTemplate<>();
        // key采用String的序列化方式
        redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
        // value序列化方式采用jackson
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
        // hash的key也采用String的序列化方式
        redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8);
        //hash的value序列化方式采用jackson
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }

    /**
     * 此方法不能用@Ben注解,避免替换Spring容器中的同类型对象
     */
    public GenericJackson2JsonRedisSerializer serializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

现在我们再继续测试一下上面的代码,看下这次存入的数据格式是否是我们想要的:发现这次存入的是JSON数据

四、Redis工具类 

我们在SpringBoot中去操作Redis总是要写这么几个方法:

那么当我们频繁操作Redis的时候,这就会显得代码很冗余,加大了维护成本!

所以我们要编写自己的工具类去封装Redis中的方法!

这里只截取一部分,只要注入我们的redisTemplate,封装里面常用的的方法即可:

utils.RedisUtil.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName RedisUtil
 * @Author zhangzhixi
 * @Description redis工具类
 * @Date 2022-4-29 10:29
 * @Version 1.0
 */
@Service
public class RedisUtil {
    @Autowired
    private RedisTemplate redisTemplate;

    private static final double SIZE = Math.pow(2, 32);


    /**
     * 写入缓存
     *
     * @param key    键
     * @param offset 位 8Bit=1Byte
     * @return true成功 false失败
     */
    public boolean setBit(String key, long offset, boolean isShow) {
        boolean result = false;
        try {
            ValueOperations operations = redisTemplate.opsForValue();
            operations.setBit(key, offset, isShow);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 获取缓存
     *
     * @param key    键
     * @param offset 位 8Bit=1Byte
     * @return true成功 false失败
     */
    public boolean getBit(String key, long offset) {
        boolean result = false;
        try {
            ValueOperations operations = redisTemplate.opsForValue();
            result = operations.getBit(key, offset);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 写入缓存-字符串
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存设置时效时间
     * @param key 键
     * @param value 值
     * @param expireTime 时间(秒)
     * @return true成功 false失败
     */
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 批量删除对应的value
     *
     * @param keys 多个key
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }


    /**
     * 删除对应的value
     *
     * @param key 键
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 判断缓存中是否有对应的value
     *
     * @param key 键
     * @return true存在 false不存在
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 读取缓存
     *
     * @param key 键
     * @return*/
    public Object get(final String key) {
        Object result = null;
        ValueOperations operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 哈希 添加
     *
     * @param key     键
     * @param hashKey 项
     * @param value   值
     */
    public void hmSet(String key, Object hashKey, Object value) {
        HashOperations hash = redisTemplate.opsForHash();
        hash.put(key, hashKey, value);
    }

    /**
     * 哈希获取数据
     *
     * @param key     键
     * @param hashKey 项
     * @return*/
    public Object hmGet(String key, Object hashKey) {
        HashOperations hash = redisTemplate.opsForHash();
        return hash.get(key, hashKey);
    }

    /**
     * 列表添加
     *
     * @param k 键
     * @param v 值
     */
    public void lPush(String k, Object v) {
        ListOperations list = redisTemplate.opsForList();
        list.rightPush(k, v);
    }

    /**
     * 列表获取
     *
     * @param k  键
     * @param l  起始位置
     * @param l1 结束位置
     * @return*/
    public List lRange(String k, long l, long l1) {
        ListOperations list = redisTemplate.opsForList();
        return list.range(k, l, l1);
    }

    /**
     * 集合添加
     *
     * @param key   键
     * @param value 值
     */
    public void add(String key, Object value) {
        SetOperations set = redisTemplate.opsForSet();
        set.add(key, value);
    }

    /**
     * 集合获取
     *
     * @param key 键
     * @return 集合
     */
    public Set setMembers(String key) {
        SetOperations set = redisTemplate.opsForSet();
        return set.members(key);
    }

    /**
     * 有序集合添加
     *
     * @param key    键
     * @param value  值
     * @param scoure
     */
    public void zAdd(String key, Object value, double scoure) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        zset.add(key, value, scoure);
    }

    /**
     * 有序集合获取
     *
     * @param key     键
     * @param scoure
     * @param scoure1
     * @return
     */
    public Set rangeByScore(String key, double scoure, double scoure1) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        redisTemplate.opsForValue();
        return zset.rangeByScore(key, scoure, scoure1);
    }


    //第一次加载的时候将数据加载到redis中
    public void saveDataToRedis(String name) {
        double index = Math.abs(name.hashCode() % SIZE);
        long indexLong = new Double(index).longValue();
        boolean availableUsers = setBit("availableUsers", indexLong, true);
    }

    //第一次加载的时候将数据加载到redis中
    public boolean getDataToRedis(String name) {

        double index = Math.abs(name.hashCode() % SIZE);
        long indexLong = new Double(index).longValue();
        return getBit("availableUsers", indexLong);
    }

    /**
     * 有序集合获取排名
     *
     * @param key   集合名称
     * @param value 值
     */
    public Long zRank(String key, Object value) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        return zset.rank(key, value);
    }


    /**
     * 有序集合获取排名
     *
     * @param key 集合名称
     */
    public Set> zRankWithScore(String key, long start, long end) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        Set> ret = zset.rangeWithScores(key, start, end);
        return ret;
    }

    /**
     * 有序集合添加
     *
     * @param key   键
     * @param value 值
     */
    public Double zSetScore(String key, Object value) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        return zset.score(key, value);
    }


    /**
     * 有序集合添加分数
     *
     * @param key
     * @param value
     * @param scoure
     */
    public void incrementScore(String key, Object value, double scoure) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        zset.incrementScore(key, value, scoure);
    }


    /**
     * 有序集合获取排名
     *
     * @param key 键
     */
    public Set> reverseZRankWithScore(String key, long start, long end) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        Set> ret = zset.reverseRangeByScoreWithScores(key, start, end);
        return ret;
    }

    /**
     * 有序集合获取排名
     *
     * @param key 集合名称
     */
    public Set> reverseZRankWithRank(String key, long start, long end) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        Set> ret = zset.reverseRangeWithScores(key, start, end);
        return ret;
    }
}