SpringBoot整合Redis详细教程
系列文章目录
(一)Redis(windows+Linux)安装及入门教程 - 掘金 (juejin.cn)
(二)Redis中的五大数据类型 - 掘金 (juejin.cn)
(三)Redis中的三种特殊类型 - 掘金 (juejin.cn)
前言
本文先介绍了 Redis 官方首选的 Java 客户端开发包Jedis(Java操作Redis的原生API),然后进行了SpringBoot对Redis的整合,自定义了RedisTemplate,以及封装了一个RedisUtils,如果只需要看SpringBoot整合Redis可以直接跳至第二小节,如果喜欢本文的话可以收藏+点赞+关注φ(゜▽゜*)♪
一、使用Jedis🥩
什么是Jedis
Jedis是一个Java实现的Redis客户端连接工具。它允许Java开发者通过Java代码轻松地与Redis数据库进行交互。
Jedis提供了与Redis交互的多种方法,这些方法与在Redis命令行中的命令基本相同,但添加了一些额外的便捷方法。
相较于Redisson,Jedis更接近于原生的Redis操作,而Redisson则更适用于分布式应用,提供了分布式锁以及其他多种数据结构。
测试
创建一个Maven工程
引入依赖
redis.clients jedis 4.4.3 com.alibaba fastjson 2.0.7
编码测试
连接数据库
package com; import redis.clients.jedis.Jedis; public class TestPing { public static void main(String[] args) { //new Jedis对象 Jedis jedis = new Jedis("127.0.0.1", 6379); //jedis中有redis中的所有命令 System.out.println(jedis.ping()); } }
常用API(所有函数与命令相同)
package com; import redis.clients.jedis.Jedis; public class TestPing { public static void main(String[] args) { //new Jedis对象 Jedis jedis = new Jedis("127.0.0.1", 6379); //jedis中有redis中的所有命令 System.out.println("清空当前数据库" + jedis.flushDB());//flushall,是删除所有数据库中的key System.out.println("判断某个键是否存在" + jedis.exists("username")); System.out.println("新增键值对" + jedis.set("username", "keye")); System.out.println("新增键值对" + jedis.set("test", "test")); System.out.println("获取键值对" + jedis.get("username")); System.out.println("获取所有键" + jedis.keys("*")); System.out.println("删除键值对" + jedis.del("username")); System.out.println("获取键存储的值类型" + jedis.type("test")); System.out.println("重命名key" + jedis.rename("test", "new_test")); System.out.println("切换数据库" + jedis.select(1)); System.out.println("查看当前数据库中key的数量" + jedis.dbSize()); jedis.close(); } }
Jedis事务操作
package com; import com.alibaba.fastjson.JSONObject; import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; /** * @Author YZK * @Date 2023/9/23 */ public class TestTx { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); JSONObject jsonObject = new JSONObject(); jsonObject.put("username", "kkkk"); jsonObject.put("password", "123456"); //开启事务 Transaction multi = jedis.multi(); try { multi.set("user", jsonObject.toJSONString()); multi.exec(); //执行事务 } catch (Exception e) { multi.discard(); //放弃事务 throw new RuntimeException(e); } finally { System.out.println(jedis.get("user")); jedis.close(); //关闭连接 } } }
二、SpringBoot整合Redis🥓
创建SpringBoot工程
SpringBoot在2.x之后的版本,将Jeris替换成了lettuce
由于Jedis 是直连模式,在多个线程间共享一个 Jedis 实例时是线程不安全的,每个线程都去拿自己的 Jedis 实例,当连接数量增多时,物理连接成本就较高了。
Lettuce的连接是基于Netty的,连接实例可以在多个线程间共享,一个多线程的应用可以使用同一个连接实例,而不用担心并发线程的数量。通过异步的方式可以让我们更好地利用系统资源。
源码分析
RedisAutoConfiguration中
@AutoConfiguration @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(RedisConnectionDetails.class) PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties) { return new PropertiesRedisConnectionDetails(properties); } @Bean @ConditionalOnMissingBean(name = "redisTemplate") //可以自定义一个redisTemplate来替换默认的 @ConditionalOnSingleCandidate(RedisConnectionFactory.class) //默认的RedisTemplate没有过多的设置,redis对象需要序列化 //两个泛型都是Object类型,过后需要强转 public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(RedisConnectionFactory.class) //由于String类型是redis中最常使用的类型,所以单独抽出了一个Bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { return new StringRedisTemplate(redisConnectionFactory); } }
RedisTemplate中
序列化配置
默认序列化方式
测试
配置文件
spring.data.redis.host=127.0.0.1 spring.data.redis.port=6379
测试
@SpringBootTest class RedisSpringbootApplicationTests { @Resource RedisTemplate redisTemplate; @Test void contextLoads() { //opsForList()操作list //opsForValue()操作String redisTemplate.opsForList().rightPush("list", "value"); redisTemplate.opsForValue().set("key", "value"); } }
保存对象(需要序列化)
@NoArgsConstructor @AllArgsConstructor @Data public class User { private String name; private int age; }
@SpringBootTest class RedisSpringbootApplicationTests { @Resource private RedisTemplate redisTemplate; @Test void contextLoads() throws JsonProcessingException { //真实开发都是使用JSON来传递对象 User user = new User("kkk", 32); //此行代码省略的前提是User对象实现Serializable接口,否则会报错 String jsonUser = new ObjectMapper().writeValueAsString(user); redisTemplate.opsForValue().set("user", jsonUser); System.out.println(redisTemplate.opsForValue().get("user")); } }
自定义RedisTemplate
@Configuration public class RedisConfig { @Bean public RedisTemplate redisTemplateSelf(RedisConnectionFactory redisConnectionFactory) { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY); genericJackson2JsonRedisSerializer.serialize(om); //String序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key、hash的key 采用 String序列化方式 template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); // value、hash的value 采用 Jackson 序列化方式 template.setValueSerializer(genericJackson2JsonRedisSerializer); template.setHashValueSerializer(genericJackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
代码解释:
查看RedisSerializer接口查看序列化实现方式
测试使用自定义的RedisTemplate
@SpringBootTest class RedisSpringbootApplicationTests { @Autowired @Qualifier(value = "redisTemplateSelf") private RedisTemplate redisTemplate; @Test void contextLoads() throws JsonProcessingException { User user = new User("kkk", 32); redisTemplate.opsForValue().set("user", user); System.out.println(redisTemplate.opsForValue().get("user")); } }
未自定义RedisTemplate
使用了自定义RedisTemplate
封装RedisTemplate
封装RedisTemplate是为了简化原生API的操作,此处代码较长,直接复制使用即可
package com.redis.utils; import jakarta.annotation.Resource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; @Component public class RedisUtils { @Resource(name = "redisTemplateSelf") private RedisTemplate 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 Optional.ofNullable(redisTemplate.getExpire(key, TimeUnit.SECONDS)).orElse(0L); } /** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return Optional.ofNullable(redisTemplate.hasKey(key)).orElse(false); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * * @param keys 可以传一个值 或多个 */ public boolean del(List keys) { if (!keys.isEmpty()) { final Long aLong = Optional.ofNullable(redisTemplate.delete(keys)).orElse(0L); return aLong > 0; } return false; } // ============================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 Optional.ofNullable(redisTemplate.opsForValue().increment(key, delta)).orElse(0L); } /** * 递减 * * @param key 键 * @param delta 要减少几(小于0) */ public long decr(String key, long delta) { if (delta o.size(key)).orElse(0L); } catch (Exception e) { e.printStackTrace(); return 0L; } return size; } /** * HashSet 并设置时间 * * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map 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 map 对应多个键值 * @return true 成功 false 失败 */ public boolean hset(String key, Map map) { try { redisTemplate.opsForHash().putAll(key, map); 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); } // ===============================list================================= /** * 通过索引 获取list中的值 * * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 */ public boolean lSet(String key, List value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) */ public boolean lSet(String key, List 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 值 */ public boolean lUpdate(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 { return Optional.ofNullable(redisTemplate.opsForList().remove(key, count, value)).orElse(0L); } catch (Exception e) { e.printStackTrace(); return 0; } } // ============================set============================= /** * 根据key获取Set中的所有值 * * @param key 键 */ public Set sMember(String key) { return redisTemplate.opsForSet().members(key); } /** * 根据value从一个set中查询,是否存在 * * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sIsMember(String key, Object value) { return Optional.ofNullable(redisTemplate.opsForSet().isMember(key, value)).orElse(false); } /** * 将数据放入set缓存 * * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sAdd(String key, Object... values) { return Optional.ofNullable(redisTemplate.opsForSet().add(key, values)).orElse(0L); } /** * 将set数据放入缓存 * * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetWithTime(String key, long time, Object... values) { try { if (time > 0) { expire(key, time); } return Optional.ofNullable(redisTemplate.opsForSet().add(key, values)).orElse(0L); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 随机移除并返回一个元素 * * @param key 键 * @return 被移除的元素 */ public Object sPop(String key) { return redisTemplate.opsForSet().pop(key); } /** * 随机返回指定长度的count个元素 * * @param key 键 * @param count 返回元素的个数 * @return 返回的元素 */ public List sRandMember(String key, long count) { return redisTemplate.opsForSet().randomMembers(key, count); } /** * 两个set集合求交集 * * @param key1 其中一个set的key * @param key2 另一个set的key * @return 两个set的差集 */ public Set sDiff(String key1, String key2) { return redisTemplate.opsForSet().difference(key1, key2); } /** * 两个set集合求交集 * * @param key1 其中一个set的key * @param key2 另一个set的key * @return 两个set的交集 */ public Set Sinter(String key1, String key2) { return redisTemplate.opsForSet().intersect(key1, key2); } /** * 两个set集合求并集 * * @param key1 其中一个set的key * @param key2 另一个set的key * @return 两个set的并集 */ public Set sUnion(String key1, String key2) { return redisTemplate.opsForSet().union(key1, key2); } /** * 获取set缓存的长度 * * @param key 键 */ public long sGetSetSize(String key) { return Optional.ofNullable(redisTemplate.opsForSet().size(key)).orElse(0L); } /** * 移除值为value的 * * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { return Optional.ofNullable(redisTemplate.opsForSet().remove(key, values)).orElse(0L); } // ============================sorted set============================= /** * 添加元素到zSet集合当中去 * * @param key 键 * @param value 值 * @param score 分数 * @return 是否添加成功 */ public boolean zAdd(String key, Object value, double score) { return Optional.ofNullable(redisTemplate.opsForZSet().add(key, value, score)).orElse(false); } /** * 返回指定分数范围的数据 * * @param key 键 * @param start 起始位置 * @param end 结束位置 * @return 指定分数范围内的zSet */ public Set zRange(String key, long start, long end) { return redisTemplate.opsForZSet().range(key, start, end); } }
测试RedisUtils
@SpringBootTest class RedisSpringbootApplicationTests { @Resource RedisUtils redisUtils; @Test void contextLoads() { User user = new User("kkk", 32); // redisTemplate.opsForValue().set("user", user); redisUtils.set("user", user); // System.out.println(redisTemplate.opsForValue().get("user")); System.out.println(redisUtils.get("user")); } } //运行结果 User(name=kkk, age=32)
参考资料
狂神说JavaRedis最新超详细版教程通俗易懂_哔哩哔哩_bilibili
springboot整合jedis (lettuce)_jedis 转 lettuce_高大王竟然被注册的博客-CSDN博客
基于RedisTemplate封装的工具类_redistemplate 包装为 工具类_花开不识君的博客-CSDN博客