likes
comments
collection
share

Redis进阶(九)—— Spring Boot 整合

作者站长头像
站长
· 阅读数 43

这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战

1、常用的 Redis 客户端介绍

在 Spring Boot 2.x 之后,对Redis连接的支持,默认采用了 lettuce。

Jedis api 在线网址:tool.oschina.net/uploads/api…

lettuce 官网地址:lettuce.io

概念:

Jedis:是老牌的Redis的Java实现客户端,提供了比较全面的Redis命令的支持;

Redisson:实现了分布式的可扩展的Java数据结构;

Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

优点:

Jedis:比较全面的提供了Redis的操作特性;

Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列;

Lettuce:基于Netty框架的时间驱动的通信层,其方法调用是异步的,Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。

2、Spring Boot 整合 Jedis

我们在使用Spring Boot搭建微服务的时候,在很多时候还是需要 redis 的高速缓存来缓存一些数据,存储一些高频率访问的数据,如果直接使用redis的话又比较麻烦,这里使用jedis来实现redis缓存来达到高效缓存的目的。

2.1 引入 Jedis 依赖

 <dependency>
     <groupId>redis.clients</groupId>
     <artifactId>jedis</artifactId>
 </dependency>

2.2 配置 application.yml

 spring:
     redis:
         port: 6379
         password: 2436
         host: 112.124.1.187
         jedis:
             pool:
                 max-idle: 6     # 连接池最大空闲连接数,默认 8
                 max-active: 10  # 连接池最大连接数(使用负值便是没有限制),默认 8
                 min-idle: 2     # 连接池最小空闲连接数,默认 0
         timeout: 2000

Spring Boot 没有整合 Jedis,所以需要自己写配置类,配置 JedisPool

2.3 编写Config

 @Configuration
 public class JedisConfig {
 
     private Logger logger = LoggerFactory.getLogger(JedisConfig.class);
 
     @Value("${spring.redis.port}")
     private Integer port;
     @Value("${spring.redis.host}")
     private String host;
     @Value("${spring.redis.password}")
     private String password;
     @Value("${spring.redis.jedis.pool.max-idle}")
     private Integer maxIdle;
     @Value("${spring.redis.jedis.pool.max-active}")
     private Integer maxActive;
     @Value("${spring.redis.jedis.pool.min-idle}")
     private Integer minIdle;
     @Value("${spring.redis.timeout}")
     private Integer timeout;
 
 
     @Bean
     public JedisPool jedisPool(){
         JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
         jedisPoolConfig.setMaxIdle(maxIdle);
         jedisPoolConfig.setMinIdle(minIdle);
         jedisPoolConfig.setMaxTotal(maxActive);
 
         JedisPool jedisPool = new JedisPool(jedisPoolConfig,host,port,timeout,password);
 
         logger.info("Jedis连接成功:" + host + ":" + port);
 
         return jedisPool;
     }
 
 }

2.4 测试1: String 类型

需求:用户输入一个key 先判断Redis中是否存在该数据 如果存在,在Redis中进行查询,并返回 如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回

 // UserService.java
 public interface UserService {
 
     /**
      * 需求:用户输入一个key
      * 先判断Redis中是否存在该数据
      *  如果存在,在Redis中进行查询,并返回
      *  如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回
      *
      */
     public String getString(String key);
 
 }
 =======================
 // UserServiceImpl.java
 @Service
 @Log    // 相当于 Logger logger = LoggerFactory.getLogger(JedisConfig.class);
 public class UserServiceImpl implements UserService {
 
     @Autowired
     JedisPool jedisPool;
 
 
     @Override
     public String getString(String key) {
 
         String value = "";
         // 1.得到Jedis对象
         Jedis jedis = jedisPool.getResource();
         // 2.判断该key在redis中是否存在
         if(jedis.exists(key)){
             // 2.1 Redis中存在,
             value = jedis.get(key);
             log.info("查询Redis中的数据!");
         } else{
             // 2.2 Redis中不存在,从数据库中查询,并存入Redis中
             value = "MySQL中的数据";
             log.info("查询MySQL中的数据: " + value);
             jedis.set(key,value);
         }
         // 3. 关闭Jedis连接
         jedis.close();
         return value;
     }
 }
 =======================
 // UserController.java
 @Controller
 public class UserController {
 
     @Autowired
     UserService userService;
 
     @RequestMapping("/getString")
     @ResponseBody
     public String getString(String key){
         return userService.getString(key);
     }
 
 }

2.5 工具类

 // JedisUtil.java
 @Component
 public class JedisUtil {
 
     @Autowired
     private JedisPool jedisPool;
 
     /**
      * 获取Jedis资源
      * @return
      */
     public Jedis getJedis(){
         return jedisPool.getResource();
     }
 
     /**
      * 释放Jedis连接
      */
     public void close(Jedis jedis){
         if(jedis!=null){
             jedis.close();
         }
     }
     ......
 
 }
 

2.6 测试2 :String类型

需求:用户输入一个redis数据,该key的有效期为 30 秒

 // UserService.java
 public interface UserService {
 
     /**
      * 测试String类型
      * 需求:用户输入一个redis数据,该key的有效期为 30 秒
      */
     public String expireStr(String key,String value);
 }
 =======================
 // UserServiceImpl.java
 @Service
 @Log    // 相当于 Logger logger = LoggerFactory.getLogger(JedisConfig.class);
 public class UserServiceImpl implements UserService {
 
     @Autowired
     JedisPool jedisPool;
 
     @Autowired
     JedisUtil jedisUtil;
     /**
      * 测试String类型
      * 需求:用户输入一个redis数据,该key的有效期为 30 秒
      */
     public String expireStr(String key,String value){
         Jedis jedis = jedisUtil.getJedis();
 
         if(!jedis.exists(key)){
             // 1.在Redis中存入数据
             jedis.set(key,value);
             // 2.设置该值过期时间
             jedis.expire(key,30);
             log.info("将" + key + "有效时间设置为:30秒。");
         }
         // 3.查询key的有效时间
         Long time = jedis.ttl(key);
 
         jedisUtil.close(jedis);
         return "该" + key + " : " + value + "的有效时间剩余: " + time;
     }
 
 }
 =======================
 @RestController
 public class UserController {
 
     @Autowired
     UserService userService;
 
     @RequestMapping("/expireStr")
     public String expireStr(String key,String value){
 
         return userService.expireStr(key,value);
     }
 
 }

2.7 测试3 :Hash类型

需求:根据用户 ID 查询用户信息

先判断是否在 Redis 中存在:

如果存在,直接从 Redis 中取出;

如果不存在,从 MySQL中取出,并存入 Redis 中

 // User.java
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class User implements Serializable {
 
     private String id;
     private String name;
     private Integer age;
 
 }
 
 =======================
 public interface UserService {
 
     /**
      * 测试Hash类型
      * 需求:根据用户ID查询用户信息
      *  先判断是否在Redis中存在:
      *      如果存在,直接从Redis中取出
      *      如果不存在,从MySQL中取出,并存入Redis中
      */
     public User findById(String id);
 
 }
 =======================
 /* Hash 测试*/
 @Service
 @Log    // 相当于 Logger logger = LoggerFactory.getLogger(JedisConfig.class);
 public class UserServiceImpl implements UserService {
 
     @Autowired
     JedisPool jedisPool;
     @Autowired
     JedisUtil jedisUtil;
     
     @Override
     public User findById(String id) {
         String key = "user:" + id;  // 实体类名称:id
         Jedis jedis = jedisUtil.getJedis();
         User user = null;
 
         if(jedis.exists(key)){  // 存在
             user = new User();
             Map<String, String> map = jedis.hgetAll(key);
             user.setId(map.get("id"));
             user.setName(map.get("name"));
             user.setAge(Integer.parseInt(map.get("age")));
             log.info("===================》从Redis中查询数据");
         } else{     // 不存在
             // 从MySQL中查询数据
             user = new User(id,"xiaojian",22);
             log.info("===================》从MySQL中查询数据" + user);
             // 存入Redis
             Map<String, String> map = new HashMap<>();
             map.put("id",user.getId());
             map.put("name",user.getName());
             map.put("age",user.getAge()+"");
 
             jedis.hmset(key,map);
             log.info("===================》存入Redis中");
 
         }
 
         jedisUtil.close(jedis);
         return user;
     }
 
 }
 
 =======================
 @RestController
 public class UserController {
 
     @Autowired
     UserService userService;
 
     @RequestMapping("/findById")
     public String findById(String id){
         return userService.findById(id).toString();
     }
 
 }

3、Spring Boot 2.x 整合 lettuce

Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

基于Netty框架的时间驱动的通信层,其方法调用是异步的,Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。

3.1 导入依赖

 <!-- 默认是lettuce客户端-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>
 <!-- redis依赖 commons-pool ,这个依赖一定要加-->
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-pool2</artifactId>
         </dependency>

3.2 配置文件

 spring:
   redis:
     port: 6379
     password: 2436
     host: 112.124.1.187
     lettuce:
       pool:
         max-active: 10
         max-idle: 6
         min-idle: 2
         max-wait: 1000
       shutdown-timeout: 100

3.3 配置类

 // 添加使用RedisTemplate模板,不书写,使用Spring Boot 默认
 @Configuration
 public class RedisConfig {
 
     @Bean
     public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
         RedisTemplate<String,Object> template = new RedisTemplate();
         template.setConnectionFactory(factory);
 
         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
         ObjectMapper om = new ObjectMapper();
         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
         jackson2JsonRedisSerializer.setObjectMapper(om);
 
         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
 
         // 在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式
         // key 采用String的序列化方式
         template.setKeySerializer(stringRedisSerializer);
         // value 采用jackson的序列化方式
         template.setValueSerializer(jackson2JsonRedisSerializer);
         
         // hash 的key 也采用String的序列化方式
         template.setHashKeySerializer(stringRedisSerializer);
         // hash 的value采用jackson的序列化方式
         template.setHashValueSerializer(jackson2JsonRedisSerializer);
 
         template.afterPropertiesSet();
 
         return template;
     }
 }

3.3.1 配置类问题

     @Bean
     public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
         RedisTemplate<String,Object> template = new RedisTemplate();
         template.setConnectionFactory(factory);
 
         // ****** 改2 ******
         GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
 
         // Jackson 格式
         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
         ObjectMapper om = new ObjectMapper();
         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         // 方法过期,改 1 时注释掉这里,正常 或 改 2 时使用
 //        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
         // ****** 改1 ******,其他情况下注释掉
         om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
         jackson2JsonRedisSerializer.setObjectMapper(om);
 
         // String 类型格式
         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
         // 在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式
         // key 采用String的序列化方式
         template.setKeySerializer(stringRedisSerializer);
         // value 采用jackson的序列化方式,使用 ****** 改 2 ****** 对象
         template.setValueSerializer(genericJackson2JsonRedisSerializer);
 
         // hash 的key 也采用String的序列化方式
         template.setHashKeySerializer(stringRedisSerializer);
         // hash 的value采用jackson的序列化方式,使用 ****** 改 2 ****** 对象
         template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
 
         template.afterPropertiesSet();
 
         return template;
     }

hash 数据类型

  1. 原始配置文件得出结果(RedisDesktopManager显示的):
 ["com.xiaojian.pojo.User",{"id":"1103","name":"修心","age":22}]
  1. 改 1
 {"id":"1105","name":"修心","age":22}
  1. 改 2
 {"@class":"com.xiaojian.pojo.User","id":"1106","name":"修心","age":22}

3.4 测试

 ========
 @Service
 @Slf4j
 public class UserServiceImpl {
 
     @Autowired
     private RedisTemplate<String,Object> redisTemplate;
 
 
     public String getString(){
         System.out.println(redisTemplate);
         log.info("RedisTemplate--------->测试");
 
         return null;
     }
 }
 
 ==========
 @SpringBootTest
 class BootLettuceApplicationTests {
 
     @Autowired
     private UserServiceImpl userService;
 
     @Test
     void contextLoads() {
         userService.getString();
     }
 
 }

PS: linux中查询到的中文以十六进制显示,可以通过在 redis-cli 后加 --raw,登录客户端

 [root@xiaojian bin]# ./redis-cli -a 2436 --raw

3.5 测试1:String 类型

需求:用户输入一个key 先判断Redis中是否存在该数据 如果存在,在Redis中进行查询,并返回 如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回

 @Service
 @Slf4j
 public class UserServiceImpl {
 
     @Autowired
     private RedisTemplate<String,Object> redisTemplate;
 
     /**
      * Lettuce --> RedisTemplate 进一步的封装
      * RedisTemplate 的方法和命令不一样
      *
      * Redis String 类型
      * 需求:用户输入一个key
      * 先判断Redis中是否存在该数据
      *  如果存在,在Redis中进行查询,并返回
      *  如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回
      *
      * @return
      */
     public String getString(String key){
 
         String val = "";
         if(redisTemplate.hasKey(key)){ // exist
             val = (String) redisTemplate.opsForValue().get(key);
             log.info("-----> 从Redis中查询出数据:" + val);
         } else{
             val = "MYSQL中查询出来的数据";
             log.info("-----> 从MySQL中查询出的数据:" + val);
             redisTemplate.opsForValue().set(key,val);
             log.info("-----> 把从MySQL中查询出来的数据存入Redis");
         }
         return val;
     }
 }
 =======================
 @SpringBootTest
 class BootLettuceApplicationTests {
 
     @Autowired
     private UserServiceImpl userService;
 
     @Test
     void contextLoads() {
         String result = userService.getString("lettuce");
         System.out.println(result);
     }
 
 }

3.6 测试2:String 类型

需求:用户输入一个redis数据,该key的有效期为 30 秒

 @Service
 @Slf4j
 public class UserServiceImpl {
 
     @Autowired
     private RedisTemplate<String,Object> redisTemplate;
 
     /**
      * 测试 String 类型
      * 需求:用户输入一个redis数据,该key的有效期为20小时
      * @return
      */
     public void expireStr(String key,String value){
 
         redisTemplate.opsForValue().set(key,value);
         // 定时,可以指定单位:天,时,分,秒
         redisTemplate.expire(key,20, TimeUnit.HOURS);
 
     }
 }
 
 ================
 @SpringBootTest
 class BootLettuceApplicationTests {
 
     @Autowired
     private UserServiceImpl userService;
 
     @Test
     void t2() {
         userService.expireStr("timeout","午时已到!");
     }
 
 }

3.7 测试3:Hash类型,(id必须为字符串)

需求:根据用户 ID 查询用户信息

先判断是否在 Redis 中存在:

如果存在,直接从 Redis 中取出;

如果不存在,从 MySQL 中取出,并存入 Redis 中

 // 首先,在 RedisConfig 类中添加hash的序列化配置
 ...
         // hash 的key 也采用String的序列化方式
         template.setHashKeySerializer(stringRedisSerializer);
         // hash 的value采用jackson的序列化方式
         template.setHashValueSerializer(jackson2JsonRedisSerializer);
 ...
     
 ================
 @Service
 @Slf4j
 public class UserServiceImpl {
 
     @Autowired
     private RedisTemplate<String,Object> redisTemplate;
 
     /**
      * 测试 Hash
      * @param id
      * @return
      *
      * 根据 Id 查询用户对象信息
      * 先判断Redis中是否存在该key
      * 如果不存在,查询MySQL 数据库,并将结果添加到 Redis 中,并返回
      * 如果存在,直接将结果在Redis查询并返回
      */
     public User findById(String id){
 
         User user = null;
 
         if(redisTemplate.opsForHash().hasKey("user",id)){
             log.info("----->从 Redis 中取出数据!");
             user = (User)redisTemplate.opsForHash().get("user",id);
         } else{
             // 从 MySQL 中取出数据
             user = new User();
             user.setId(id);
             user.setName("修心");
             user.setAge(22);
             log.info("----->从 MySQL 中取出数据");
 
             /**
                 @ param h 用户实体,user
                 @ param hk 用户主键
                 @ param hv 整个对象
              */
             redisTemplate.opsForHash().put("user",id,user);
             log.info("----->将 map 数据存入 Redis中");
         }
              return user;
     }
 }
 
 ========================
 @SpringBootTest
 class BootLettuceApplicationTests {
 
     @Autowired
     private UserServiceImpl userService;
 
     @Test
     void t3() {
         User user = userService.findById("1143");
         System.out.println(user);
     }
 
 }

PS: 问题

问题1:出现了许多相同的字符串 ---- > 提取出来

解1:工具类

解2:实体Bean声明一个返回该本类字符串的方法

问题2:强制类型转换问题 以及 重复书写很长一段 redisTemplate.opsForHash()

解:在业务类上方声明一下变量,用变量名替换 redisTemplate.opsForHash()

     @Resource(name = "redisTemplate")
     private ValueOperations<String, String> redisString;
 
     @Resource(name = "redisTemplate")
     private HashOperations<String, String, User> redisHash;    // K:"user"; HK:"ID"; HV: Object

4、Redis 常见应用

4.1 手机验证功能

需求:

用户在客户端输入手机号,点击发送后随即生成四位数字码,有效期60秒

输入验证码,点击验证,返回成功或者失效,且每个手机号在5分钟内只能验证3次。并给相应信息提示

4.2 限制登录功能

需求:

用户在2分钟内,仅允许输入错误密码5次;

如果超过次数,限制其登录1小时。(要求每登录失败时,都要给相应提示)

转载自:https://juejin.cn/post/6999216009596911623
评论
请登录