天天看点

SpringBoot2.x操作-Redis(Lettuce)-03

Redis(lettuce)

Jedis的封装-----> ​

​RedisTemplate​

​​

Jedis的封装-----> Jedis工具类----->JedisUtil

Java代码操作Redis,需要使用Jedis,也就是Redis支持的第三方类库
注意:Jedis2.7以上的版本才支持集群的操作      

文章目录

  • ​​Redis(lettuce)​​
  • ​​一、使用SpringBoot连接配置Redis​​
  • ​​1.1 Maven配置​​
  • ​​1.2 编写连接参数的yml​​
  • ​​1.3 测试连接是否成功​​
  • ​​二、SpringBoot整合RedisConfig​​
  • ​​2.1 opsForValue​​
  • ​​2.2 RedisTemplate源码剖析​​
  • ​​2.3 自定义序列化类​​
  • ​​2.4 Redistemplate模拟序列化​​
  • ​​2.5 OpsForHash​​
  • ​​2.6 序列化Hash类型的KV​​
  • ​​2.7 改造业务实现方法​​
  • ​​三、案例 :限制登录功能​​
  • ​​3.1 目的:防止暴力破解​​
  • ​​3.2 思路​​

一、使用SpringBoot连接配置Redis

1.1 Maven配置

新建一个SpringBoot2.x的项目,在Maven的pom.xml的文件里加入以下依赖

<dependencies>
    <!--SpringBoot的启动器-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--热部署工具-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <!--lombok-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <!--RedisTemplate 默认是Lettuce客户端-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--redis依赖于commons_pool2 这个以来也一定要加-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
    </dependency>
    <!--测试的依赖库文件-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>      

1.2 编写连接参数的yml

server:
  port: 8089
  
spring:
  application:
    name: redistemplate
  redis:
    host: 192.168.188.129
    port: 6379
    database: 0
    jedis:
      pool:
        max-active: 10
        max-idle: 10
        min-idle: 1      

​注意:这里的因为采用的原生的SpringBoot操作Redis,所以不需要写配置类,因为SpringBoot1.x版本,SpringBoot已经帮我们做好了封装​

1.3 测试连接是否成功

@SpringBootTest
class DhcRedisProjectLettuceApplicationTests {
  
  @Autowired
  private RedisTemplate<String, String> redisTemplate;

  @Test
  void contextLoads() {
    System.out.println(redisTemplate);
  }
}      
SpringBoot2.x操作-Redis(Lettuce)-03

连接成功,返回redisTemplate在内存中封装lettuce后的内存地址

二、SpringBoot整合RedisConfig

利用SpringBoot去整合Redis的时候,前面知道默认是使用的Lettuce来操作redispool

这是我们还需要借助一些方法去链接Redis

2.1 opsForValue

@Test
void contextLoads() {
  ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
  opsForValue.set("java", "Redis学习2022");
  String string = opsForValue.get("java");
  System.out.println(string);
}      
SpringBoot2.x操作-Redis(Lettuce)-03

可以看到我们借助redisTemplate完成了对Redis的数据存取操作,但是这是我们去Redis的客户端去查看

192.168.188.129:6379> keys *
1) "java"
2) "User:1001"
192.168.188.129:6379> get java
"Redis\xe5\xad\xa6\xe4\xb9\xa02022"
192.168.188.129:6379>      

​发现我们所存储的key-value会存在中文乱码的问题​

❓思考: 为什么在使用SpringBoot的redisTemplate里操作存取Redis的key的时候不会中乱码呢?

**​​

​原因​

​😗*因为RedisTemplate里面封装有序列化的操作,实际上redisTemplate是对jedis的封装

2.2 RedisTemplate源码剖析

打开RedisTempalte里面的源码查看一个叫做afterPropertiesSet的方法,里面有对redis操作的序列化操作

@Override
  public void afterPropertiesSet() {

    super.afterPropertiesSet();

    boolean defaultUsed = false;

    if (defaultSerializer == null) {

      defaultSerializer = new JdkSerializationRedisSerializer(
          classLoader != null ? classLoader : this.getClass().getClassLoader());
    }

    if (enableDefaultSerializer) {

      if (keySerializer == null) {
        keySerializer = defaultSerializer;
        defaultUsed = true;
      }
      if (valueSerializer == null) {
        valueSerializer = defaultSerializer;
        defaultUsed = true;
      }
      if (hashKeySerializer == null) {
        hashKeySerializer = defaultSerializer;
        defaultUsed = true;
      }
      if (hashValueSerializer == null) {
        hashValueSerializer = defaultSerializer;
        defaultUsed = true;
      }
    }

    if (enableDefaultSerializer && defaultUsed) {
      Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
    }

    if (scriptExecutor == null) {
      this.scriptExecutor = new DefaultScriptExecutor<>(this);
    }

    initialized = true;
  }      
SpringBoot2.x操作-Redis(Lettuce)-03

编写缓存配置类RedisConfig的时候用于调优缓存默认配置,RedisTemplate<String,String>的类型兼容性更高

在Spring-data-redis里的序列化类主要有以下几个:
  • **​

    ​GenericToStringSerializer:​

    ​**可将任何对象泛化为字符串并序列化
  • **Jackson2JsonRedisSerializer:**序列化Object对象为Json字符串,(与JacksonJsonRedisSerializer相同)
  • JdkSerializationRedisSerializer:序列化对象必须实现Serializier接口,是原生的JDK的序列化类(默认的序列化方式)
  • ​StringRedisSerializer:​

JdkSerializationRedisSerializer序列化,长度长且不易读取,被序列化的属性内容还有其他内容

内容长度格式类似如下:“Redis\xe5\xad\xa6\xe4\xb9\xa02022”–>Redis学习2022

Jackson2JsonRedisSerializer序列化,被序列化对象不需要实现Serializable接口,被序列化结果清晰,内容阅读,而且存储的字节数少,速度快

存储的内容如下:

2.3 自定义序列化类

上面我们通过查看源码发现,RedisTemplate里默认使用的是Jdk的序列化类,这种方式操作对象数据比较鸡肋,所以,这里我么来自定义redisTemplate的序列化类。

SpringBoot2.x操作-Redis(Lettuce)-03

新建一个RedisConfig的类完成对RedisTempalte的序列化类的自定义操作。

@Configuration
public class RedisConfig {

  @Bean  
  public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, String> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    
    // 自定义Redis的序列化
    StringRedisSerializer serializer = new StringRedisSerializer();
    template.setKeySerializer(serializer);
    template.setValueSerializer(serializer);
    return template;
  }
}      

再次测试,我们发现这是的Redis的序列化后的中文在不会出现中文乱码,但是在其客户端内查看仍然会有乱码

SpringBoot2.x操作-Redis(Lettuce)-03
SpringBoot2.x操作-Redis(Lettuce)-03
**总结:**使用RedisTmeplate的自定义的序列化类也有不足之处,但是相比于使用默认的Jdk的序列化工具来说,要有一些优势。

2.4 Redistemplate模拟序列化

​StringRedisTemplate​

​​ 的出现就是为了解决上面所出现的问题,​

​但是仅仅针对于String数据类型,​

@Service
@Slf4j
public class UserServiceImpl {

  @Autowired
  private RedisTemplate<String, Object> redisTemplate;
  /**
   * 用RedisTempalte来操作String数据类型
   */
  
  /**
   * 需求与之前一样
   */
  public String getUserName(String id) {
    // 生成key
    String key = "userName:"+id;
    ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
    if (redisTemplate.hasKey(key)) {
      log.info("查询的是Redis...");
      String value = (String)opsForValue.get(key);
      return value;
    }else {
      log.info("查询的是MySql数据库....");
      String valueString  ="赵安";
      opsForValue.set(key, valueString);
      return valueString;
    }
  }
}      

测试

@Test
  public void test2() {
    String result = userServiceImpl.getUserName("1001");
    System.out.println(result);
  }      
SpringBoot2.x操作-Redis(Lettuce)-03
SpringBoot2.x操作-Redis(Lettuce)-03

2.5 OpsForHash

OpsForHash是一个用来操作Hash数据类型的方法,其可以完成对Java对象的存取。

业务方法的实现

/**
   * 用RedisTempalte来操作Hash数据类型
   */
  public User selectByUserId(String id) {
    // String key = "User:"+id;
    // HashOperations<H, HK, HV> H:表示user;HK表示id;HV表示存储的User对象
    HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
    if (!hashOperations.hasKey("User", id)) {
      // 查询MySQL 不存在
      log.info("查询的是MySQL数据库...");
      User user = new User();
      user.setId("1002");
      user.setName("辛迪");
      user.setRemark("是一个勤奋的好孩子...");
      
      
      hashOperations.put("User", id, user);
      
      return user;
    }else {
      log.info("查询的是Redis数据库...");
      User result = (User)hashOperations.get("User", id);
      return result;
    }
  }      

测试业务方法

@Test
  public void testHash() {
    User user = userServiceImpl.selectByUserId("1002");
    System.out.println(user);
  }      
SpringBoot2.x操作-Redis(Lettuce)-03

但是这是我们去客户端中查看,发现我们的中文是有乱码的,所以我们继续去RedisCnofig的类里去自定义修改Hash数据类型的key-value的序列化类。

2.6 序列化Hash类型的KV

​这里我们就需要使用到在上面所讲到的Jackson序列化的类,来实现对Hash数据类型的数据的序列化存取操作​

@Bean  
  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    
    // 自定义Redis的序列化
    StringRedisSerializer serializer = new StringRedisSerializer();
    // 自定义Hash类型的Value的序列化类
    GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
    // 自定义Hash的数据类型的KEY-VALUE的序列化
    // KEY可以是String类型 但是值 就不可能是String类型
    template.setHashKeySerializer(serializer);
    // Value 必须要使用jason格式的序列化类
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    return template;
  }      

这是我们再次运行测试类,去查看客户端的中文是否存在乱码

SpringBoot2.x操作-Redis(Lettuce)-03

可以看到在,这里就可以查看到被Json序列化后的数据的值了。

2.7 改造业务实现方法

之前我们在操作Redis的数据类型的时候,我们往往不会修改其泛型的类型,但是,在实际的而业务开发中,需要根据所需要的数据类型来进行适当的修改,为了可以实现最佳的一个实现效果,这里我对UserServiceImpl的累计逆行修改

@Service
@Slf4j
public class UserServiceImpl {

  @Autowired
  private RedisTemplate<String, String> redisTemplate;
  /**
   * 用RedisTempalte来操作String数据类型
   */
  @Resource(name = "redisTemplate")
  ValueOperations<String, String> opsForValue;
  /**
   * 用RedisTempalte来操作Hash数据类型
   * name 必须是redisTemplate 因为RedisTemplate里面的构造方法是RedisTemplate()
   */
  @Resource(name = "redisTemplate")
  HashOperations<String, String, Object> hashOperations;

  /**
   * 需求与之前一样
   */
  public String getUserName(String id) {
    // 生成key
    String key = "userName:" + id;
    // ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
    if (redisTemplate.hasKey(key)) {
      log.info("查询的是Redis...");
    // String value = opsForValue.get(key);
      return opsForValue.get(key);
    } else {
      log.info("查询的是MySql数据库....");
      String valueString = "刘大黄";
      opsForValue.set(key, valueString);
      return valueString;
    }
  }

  /**
   * 用RedisTempalte来操作Hash数据类型
   */
  public User selectByUserId(String id) {
    // String key = "User:"+id;
    // HashOperations<H, HK, HV> H:表示user;HK表示id;HV表示存储的User对象
    // HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
    if (!hashOperations.hasKey("User", id)) {
      // 查询MySQL 不存在
      log.info("查询的是MySQL数据库...");
      User user = new User();
      user.setId("1003");
      user.setName("于文文");
      user.setRemark("是一个勤奋的好孩子...");

      hashOperations.put("User", id, user);

      return user;
    } else {
      log.info("查询的是Redis数据库...");
      User result = (User) hashOperations.get("User", id);
      return result;
    }
  }
}      
注意:在这里我使用了成员变量的方式来定义操作Redis的方法。

三、案例 :限制登录功能

需求描述:

​​

​用户在2分钟内,仅允许输入错误密码五次。​

​​

​如果超过五次,限制其登陆一小时(要求每登陆失败时,都要给出响应的提示)​

3.1 目的:防止暴力破解

明确:只要你发现有一个网站,没有验证码,我们就可以通过计算机的方式暴力破解

验证码:

3.2 思路

  • 比如希望达到的要求是这样: 在 2min 内登陆异常次数达到5次, 锁定该用户 1h
  • 那么登陆请求的参数中, 会有一个参数唯一标识一个 user, 比如 邮箱/手机号/userName
  • 用这个参数作为key存入redis, 对应的value为登陆错误的次数, string 类型, 并设置过期时间为 2min. 当获取到的 value == “4” , 说明当前请求为第 5 次登陆异常, 锁定.
  • 所谓的锁定, 就是将对应的value设置为某个标识符, 比如"lock", 并设置过期时间为 1h