- SpringBoot整合Redis
SpringBoot操作数据:spring-data jpa jdbc mongodb redis
SpringData是和SpringBoot齐名的项目
如果是SpringBoot2.0以上配置集群的话一定要使用lettuce.pool下面的属性
在SpringBoot2.x之后 jedis被替换成了lettuce
jedis和lettuce有什么区别呢?
jedis:
用作于2.0之前,底层采用的是直连的serve,多个线程操作的话是不安全的,如果想要避免这种情况,需要使用jedis的连接池来解决 更像BIO(阻塞)模式(dubbo)
lettuce:
底层采用netty(高性能的网络框架,异步请求),实例key在线程中进行共享,不存在线程不安全的情况,可以减少线程数据,性能更加的高 更像NIO模式
源码分析:
//原文:如果不存在bean才生效 也就是告诉我们可以自己定义一个RedisTemplate来替换这个默认的 @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { //默认的RedisTemplate 没有过多的设置,redis对象的保存都是需要序列化的,尤其是使用netty NIO这种异步的 //两个泛型都是object类型,后面使用需要强制转换,我们期望的数据类型应该是<string,object> RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { //由于string类型是redis最长使用的数据类型,所以单独提出来了一个方法(bean) StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
综上所述,我们整合测试一下
1.导入依赖(pom)
<!--redis依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.配置连接(application.properties(yml))
# SpringBoot所有的配置类,都有一个自动配置类 RedisAutoConfiguration # 自动配置类都会绑定一个properties配置文件 RedisProperties #配置redis的ip 如果是本机的话可以写localhost或者127.0.0.1 如果是远程的话就写远程的ip即可 spring.redis.host=localhost #配置redis的端口号 默认都是6379 spring.redis.port=6379 #配置redis使用的数据库 注:redis共有16个数据库 默认使用第0个 spring.redis.database=2 #配置redis的集群 (如果是SpringBoot2.0以上配置集群的话一定要使用lettuce.pool下面的属性) spring.redis.lettuce.pool
3.测试
package com.wyh; 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.core.RedisTemplate; @SpringBootTest class SpringBootRedis01ApplicationTests { //注入RedisTemplate @Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { //很多数据类型api命令是以OpsFor开头的,操作不同的数据类型,api用法和redis类似 //redisTemplate.opsForSet(); // 集合 //redisTemplate.opsForZSet(); //有序集合 //redisTemplate.opsForList(); //list列表 //redisTemplate.opsForHash(); //Hash哈希 //redisTemplate.opsForValue(); //字符串 //redisTemplate.opsForGeo(); //地理位置 //redisTemplate.opsForHyperLogLog(); //基数统计 //除了基本的操作,我们常用的方法都可以通过RedisTemplate操作,比如事务和基本的CRUD(增删改查) //redisTemplate.multi(); //事务 //获取Redis连接 //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); // connection.flushDb(); //刷新数据库 // connection.close(); //关闭数据库 //设置字符串 尽量不要用中文(转义) redisTemplate.opsForValue().set("name","wyh"); redisTemplate.opsForValue().set("name1","魏一鹤"); //获取字符串 System.out.println( redisTemplate.opsForValue().get("name")); System.out.println( redisTemplate.opsForValue().get("name1")); } }
网络异常,图片无法展示
|
1 序列化配置
网络异常,图片无法展示
|
默认的序列化方式是JDK序列化,对字符串转义,我们可能会使用JSON来进行序列化,这时候我们就需要自己进行配置一个redisTemplate类了
网络异常,图片无法展示
|
2 自定义RedisTemplate(自带的不能存储中文和对象,需要我们进行更改)
例子1:创建user实体,name(中文存储到redis),age存储到redis,不进行序列化
user对象实体
package com.wyh.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.stereotype.Component; import java.io.Serializable; /** * @program: SpringBoot-Redis-01 * @description: User实体 * @author: 魏一鹤 * @createDate: 2021-11-18 23:30 **/ @Data //自动get set生成(lombok依赖) @Component //进行注入到spring工厂成为一个组件,方便我们使用 @AllArgsConstructor //生成有参构造函数(lombok依赖) @NoArgsConstructor //生成无参构造函数(lombok依赖) //目前我们的User实体是没有被序列化的 //想要实现序列化只需在User后加implements Serializable(实现序列化接口) public class User { private String name; private int age; }
测试方法
//注入RedisTemplate @Autowired private RedisTemplate redisTemplate; @Test//表示该方法是测试方法(junit依赖) public void setUser() throws JsonProcessingException { //真实的开发一般传递json来传递对象,而不是直接new出来对象 User user = new User("魏一鹤", 22); //利用ObjectMapper把我们的User对象进行json格式化 String jsonUser = new ObjectMapper().writeValueAsString(user); //把json格式化后的user对象以键值对应的方式存在redis中 redisTemplate.opsForValue().set("user",jsonUser); //通过key获取user对象 System.out.println( redisTemplate.opsForValue().get("user")); }
打印输出
{"name":"魏一鹤","age":22}
redis中查看中文乱码
127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x04user" #中文乱码
例子2:创建user实体,name(中文存储到redis),age存储到redis,进行序列化
代码和上面类似,只不过传的对象从json变成实体,也就是没有经过json字符串转化直接传对象
@Test//表示该方法是测试方法(junit依赖) public void setUser() throws JsonProcessingException { //真实的开发一般传递json来传递对象,而不是直接new出来对象 User user = new User("魏一鹤", 22); //利用ObjectMapper把我们的User对象进行json格式化 // String jsonUser = new ObjectMapper().writeValueAsString(user); //把json格式化后的user对象以键值对应的方式存在redis中 //直接存储对象,而不是存储json数据类型 redisTemplate.opsForValue().set("user",user); //redisTemplate.opsForValue().set("user",jsonUser); //通过key获取user对象 System.out.println( redisTemplate.opsForValue().get("user")); }
此时会报错我们的实体User没有进行序列化
org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.wyh.entity.User] at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:96)
网络异常,图片无法展示
|
原因:我们传递的所有对象,都需要序列化(实现 implements Serializable接口),传session是可以直接传的不用经过序列化
解决:把我们的user实体进行序列化 目前企业中 我们所以的pojo(Java对象)都会进行序列化
@Data //自动get set生成(lombok依赖) @Component //进行注入到spring工厂成为一个组件,方便我们使用 @AllArgsConstructor //生成有参构造函数(lombok依赖) @NoArgsConstructor //生成无参构造函数(lombok依赖) //想要实现序列化只需在User后加implements Serializable(实现序列化接口) //目前企业中 我们所以的pojo(Java对象)都会进行序列化 public class User implements Serializable { private String name; private int age; }
再次运行发现会成功
网络异常,图片无法展示
|
不过此时的redis中我们存储的user依然是乱码
网络异常,图片无法展示
|
我们如何解决我们的序列化问题呢?
redis默认的序列化是jdk序列化,我们需要更换我们的redis模板(redis Template)
网络异常,图片无法展示
|
我们自己编写一个自定义的模板redisTemplate,可以在企业中直接使用,进行各种序列化操作
package com.wyh.condig; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; 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; /** * @program: SpringBoot-Redis-01 * @description: redis配置类,这是一个固定模板,可以在企业中直接使用 * @author: 魏一鹤 * @createDate: 2021-11-14 18:56 **/ @Configuration //配置类一般都会用到的注解,注入到spring工厂 public class RedisConfig { //编写我们自己的redisTemplate类 //自己定义了一个redisTemplate 只要有几下几个改动操作 //1把我们的双泛型从<Object,Object>改成了<String,Object>方便我们后续使用 @Bean //简介:java.lang.SuppressWarnings是J2SE5.0中标准的Annotation之一。可以标注在类、字段、方法、参数、构造方法,以及局部变量上。 //作用:告诉编译器忽略指定的警告,不用在编译完成后出现警告信息。 @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplateWYH(RedisConnectionFactory redisConnectionFactory) { //我们一般为了自己开发方便 一般直接使用<String,Object> RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); //连接工厂 template.setConnectionFactory(redisConnectionFactory); //序列化配置 //创建jackson序列化方式,并对jackson进行序列化配置 Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper objectMapper = new ObjectMapper(); //对我们的jackson对象进行转义 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper); //序列化String类型 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); //key采用String的序列化方式 template.setValueSerializer(stringRedisSerializer); //hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); //value序列化采用jaskson template.setValueSerializer(objectJackson2JsonRedisSerializer); //hash的value序列化方式采用jackson template.setHashValueSerializer(objectJackson2JsonRedisSerializer); //配置具体的序列化方式 template.setKeySerializer(objectJackson2JsonRedisSerializer); //初始化参数和初始化工作 template.afterPropertiesSet(); template.setConnectionFactory(redisConnectionFactory); return template; } }
网络异常,图片无法展示
|
然后使用我们自己写的配置模板进行序列化
@Qualifier("redisTemplateWYH") //如果有多个名称相同的 指定具体是哪一个 redisTemplateWYH是我的方法名 下面这个是我自己写的而不是框架的 字符串里面的值要和用到的那个保持一致 private RedisTemplate redisTemplate;
调用刚才的方法直接存储对象成功,然后去redis中查看,已经序列化成功
网络异常,图片无法展示
|
3 企业应用:自定义封装redisUtil
package com.wyh.utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @program: SpringBoot-Redis-01 * @description: redis工具类 * @author: 魏一鹤 * @createDate: 2021-11-21 22:26 **/ @Component @Qualifier("redisTemplateWYH") //如果有多个名称相同的类 指定具体是哪一个 public final class RedisUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; // =============================common============================ /** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key)); } } } // ============================String============================= /** * 普通缓存获取 * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * @param key 键 * @param delta 要增加几(大于0) */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * @param key 键 * @param delta 要减少几(小于0) */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } // ================================Map================================= /** * HashGet * @param key 键 不能为null * @param item 项 不能为null */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * @param key 键 * @return 对应的多个键值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * @param key 键 * @param map 对应多个键值 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并设置时间 * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除hash表中的值 * * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * * @param key 键 * @param item 项 * @param by 要增加几(大于0) */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * * @param key 键 * @param item 项 * @param by 要减少记(小于0) */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * @param key 键 */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据value从一个set中查询,是否存在 * * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将数据放入set缓存 * * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 将set数据放入缓存 * * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) expire(key, time); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * * @param key 键 */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值为value的 * * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } // ===============================list================================= /** * 获取list缓存的内容 * * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取list缓存的长度 * * @param key 键 */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通过索引 获取list中的值 * * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据索引修改list中的某条数据 * * @param key 键 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N个值为value * * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } } }
测试我们的redisutil
//注入我们的redisUtil @Autowired private RedisUtil redisUtil; //测试我们的redisUtil @Test public void testUtil() { redisUtil.set("name","wyh"); System.out.println(redisUtil.get("name")); }
redis的作者以前迷恋一个女明星,她的名称缩写用九键进行输入就是6379