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() { ValueOperationsredisTemplate = 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 RedisTemplateredisTemplate(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 { ValueOperationsoperations = 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