# 本节要点 - SpringBoot项目集成redis - 熟悉五大数据类型API - 实操练习 # 一、创建一个项目 !\[image-20251012005937327\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/java/daiwei/20251012005937429.png) # 二、加依赖 \`\`\` org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 \`\`\` # 三、加配置 \`\`\` server: port: 9090 spring: application: name: springboot-redis data: redis: host: 182.92.108.193 #redis的地址 port: 16379 #redis的端口 password: 7e40796#Tb9bbf861 #密码 database: 13 lettuce: pool: max-active: 8 #最大连接 max-idle: 8 #最大空闲连接 min-idle: 0 #最小空闲连接 max-wait: 100 #最大等待时间 \`\`\` - \*\*Jedis:同步阻塞,非线程安全\*\*:Jedis 采用同步阻塞的 I/O 模型。它的实例\*\*不是线程安全\*\*的,不能在多线程环境下直接共享。通常需要使用\*\*连接池\*\*来管理,每个线程从池中获取一个独立的 Jedis 实例进行操作。 - \*\*Lettuce:异步非阻塞,线程安全\*\*:Lettuce 基于 Netty 框架,采用异步非阻塞的 I/O 模型。它的实例是\*\*线程安全\*\*的,多个线程可以共享同一个连接,无需额外的同步措施。 # 四、配置类 核心用户就是:\*\*序列化\*\* 序列化和不序列化的区别如下 \`\`\` // 存储的数据在 Redis 中是这样的: // key: \\xac\\xed\\x00\\x05t\\x00\\x04user // value: \\xac\\xed\\x00\\x05sr\\x00\\x11com.example.User... (二进制乱码) // 配置后,数据在 Redis 中清晰可读: // key: "user:123" // value: {"id":123,"name":"张三","age":25} \`\`\` \`\`\` package 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.RedisSerializer; /\*\* \* @author daiwei \* @Description: \*/ @Configuration public class RedisConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate\<\>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(RedisSerializer.string()); //序列化内容,保证是可见 redisTemplate.setValueSerializer(RedisSerializer.json()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setHashValueSerializer(RedisSerializer.json()); return redisTemplate; } } \`\`\` stringRedisTemplate的问题 \`\`\` stringRedisTemplate.opsForValue().set("user:1", new User("X", 18)); \`\`\` # 五、controller测试 总体说明 https://docs.spring.io/spring-data/redis/reference/3.5/redis/getting-started.html \| API \| 返回类型 \| 说明 \| \| --------------------------- \| -------- \| --------------------------- \| \| redisTemplate.opsForValue() \| String \| 字符串 token、json \| \| redisTemplate.opsForHash() \| Hash \| k-\>kv对,购物车 \| \| redisTemplate.opsForList() \| List \| 队列先进先出,栈先进后 \| \| redisTemplate.opsForSet() \| Set \| 无顺序集合,好友 \| \| redisTemplate.opsForZSet() \| Zset \| 比set多了一个排序分 ,排行榜 \| \`\`\` 注入 @Autowired private RedisTemplate redisTemplate; //默认对String进行了序列化 //@Autowired //private StringRedisTemplate stringRedisTemplate; \`\`\` ## 1、String 1、获取操作String的对象 \`\`\` ValueOperations opt = redisTemplate.opsForValue(); \`\`\` 2、写一个String \`\`\` opt.set("com:woniu:token:user:1001","ddbae104173c08bfa061c1a24019423"); \`\`\` 3、json对象存放 - 自己加了序列化的RedisTemplate \`\`\` @Autowired private RedisTemplate redisTemplate; ... ValueOperations ops =redisTemplate.opsForValue(); UserInfoVo userInfoVo1 = UserInfoVo.builder() .userId("10001") .username("admin1") .password("11111") .build(); ops.set("userinfo1", userInfoVo1); \`\`\` - 用自带的StringRedisTemplate进行操作 \`\`\` @Autowired private StringRedisTemplate stringRedisTemplate; ... ObjectMapper mapper = new ObjectMapper(); UserInfoVo userInfoVo = UserInfoVo.builder() .userId("1000") .username("admin") .password("") .build(); stringRedisTemplate.opsForValue().set("userinfo", mapper.writeValueAsString(userInfoVo)); \`\`\` 4、如果想要去掉class,就需要修改配置类(\*\*可选\*\*) \`\`\` package com.example.redis131.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 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.RedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate\<\>(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer\<\>(Object.class); ObjectMapper objectMapper = JsonMapper.builder() .addModule(new JavaTimeModule()) // 支持 LocalDate/LocalDateTime .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // 时间转字符串 .build(); objectMapper.deactivateDefaultTyping(); //核心代码 serializer.setObjectMapper(objectMapper); redisTemplate.setKeySerializer(RedisSerializer.string()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setValueSerializer(serializer); // 使用上面配好的 redisTemplate.setHashValueSerializer(serializer); // Hash 也保持一致 redisTemplate.afterPropertiesSet(); //生效配置 return redisTemplate; } } \`\`\` 写入json对象 \`\`\` ValueOperations opt = redisTemplate.opsForValue(); UserInfoVo userInfoVo = UserInfoVo.builder().username("user1").password("PASSWORD").userId("1").build(); opt.set("userinfo",userInfoVo); \`\`\` 获取json对象 \`\`\` @Autowired private ObjectMapper objectMapper; //注入一个objectMapper用于转换 ... // 1. 先取出来,用 Object 接收 Object result = opt.get("userinfo"); // 2. 用之前配置好的 ObjectMapper 转成目标类 // 注意:你需要把 RedisConfig 里那个 objectMapper 注入到当前类中 UserInfoVo avo = objectMapper.convertValue(result, UserInfoVo.class); \`\`\` 3、自增加计数器 \`\`\` opt.increment("num"); \`\`\` 4、自减计数器 \`\`\` opt.decrement("num"); \`\`\` 5、分布式锁(无过期时间) \`\`\` opt.setIfAbsent("lock","lock")); \`\`\` 6、有过期时间(安全的) \`\`\` opt.setIfAbsent("wc1","有人", 15, TimeUnit.SECONDS) // 与命令行的 SET wc1 "有人" NX EX 15000 \`\`\` 7、释放锁(不安全) \`\`\` rediesTemplate.delete("key") //直接删除是非原子性,并非安全 \`\`\` 8、\*\*正确的释放锁(重点)\*\* - lua脚本的释放锁,更加的安全,一条命令是原子性的 - KEYS\[1\]代表的是加锁的key,ARGV\[1\]代表的是加锁的value - ARGV\[1\]一般用当前线程id;因为 lua脚本有判断当前的key是否和value匹配, - 用线程id就可以保障,加锁和解锁都是同一个线程;避免A线程加锁,被B线程释放掉,增加了是否是当前线程判断 KEYS\[1\]) == ARGV\[1\]。 - 完整写法如下 \`\`\` public String redisUnLock(){ String luaScript = "if redis.call('get', KEYS\[1\]) == ARGV\[1\] then " + " return redis.call('del', KEYS\[1\]) " + "else " + " return 0 " + "end"; DefaultRedisScript redisScript = new DefaultRedisScript\<\>(); redisScript.setScriptText(luaScript); redisScript.setResultType(Long.class); // 原子性地执行脚本 Long result = (Long) redisTemplate.execute(redisScript, Arrays.asList("redisLock"), "threadId-100"); } \`\`\` 加锁的时候同样需要传递加锁线程的threadId-100 \`\`\` public String redisLock(){ System.out.println("有过期时间的分布式锁测试:"+redisTemplate.opsForValue().setIfAbsent("redisLock","threadId-100", 15, TimeUnit.SECONDS)); return "redisLock"; } \`\`\` 放token的时候注意一下key的取名字: com:woniu:user:manage:token {"id",uname,.....} get("com:woniu:user:manage:"+com:woniu:user:manage:token ); TTL \`\`\` //单个kv ValueOperations opt = redisTemplate.opsForValue(); opt.set("token","userinfo"); System.out.println("set了一个test,获取试试:"+opt.get("token")); System.out.println("判断某一个key是否存在:"+redisTemplate.hasKey("test")); //+1 redisTemplate.opsForValue().increment("num"); //-1 redisTemplate.opsForValue().decrement("num"); //分布式锁 System.out.println("分布式锁测试:"+redisTemplate.opsForValue().setIfAbsent("lock","lock")); //有过期时间的分布式锁 //System.out.println("有过期时间的分布式锁测试:"+redisTemplate.opsForValue().setIfAbsent("wc1","有人", Duration.ofSeconds(30))); System.out.println("有过期时间的分布式锁测试:"+redisTemplate.opsForValue().setIfAbsent("wc1","有人", 15, TimeUnit.SECONDS));//等同于 SET key wc1 NX EX 3000 //json 方式存储 UserInfo userInfo = new UserInfo(); userInfo.setId(1); userInfo.setName("test"); userInfo.setAge(18); redisTemplate.opsForValue().set("userInfo",userInfo); \`\`\` 补充: \`\`\` // RedisTemplate 方法 对应 Redis 命令 opsForValue().set("key", "value") → SET key value opsForValue().get("key") → GET key opsForValue().increment("key") → INCR key opsForValue().append("key", "val") → APPEND key val opsForValue().setIfAbsent("k", "v")→ SETNX k v opsForValue().multiSet(map) → MSET key1 val1 key2 val2 opsForValue().getAndSet("k", "v") → GETSET k v \`\`\`
08-3阶内容-1.5.3-Spring Boot集成redis-1
https://xiaochenblog.icu/archives/08-3jie-nei-rong-1.5.3-spring-bootji-cheng-redis-1
评论