利用spring-data-redis实现incr自增的操作

 更新时间:2020年11月24日 10:40:57   作者:快乐崇拜234  
这篇文章主要介绍了利用spring-data-redis实现incr自增的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

应该有不少人在使用spring-data-redis时遇到各种各样的问题。反正我是遇到了。

由于是隔了一段时间才写的本篇博客,也懒得去重现哪些错误场景了,下面凭着记忆写了几个我遇到的问题:

redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range

使用的RedisTemplate,做读写操作时候,都是要经过序列化和反序列化。

这时你使用redisTemplate.opsForValue().increment()就可能报错redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range了。

valueOper.get(key) 获取不到自增的值。

于是我去看了一下redis的官方文档,找到一个解决方法

使用spring-data-redis实现incr自增
/**
   *
   * @param key
   * @param liveTime
   * @return
   */
  public Long incr(String key, long liveTime) {
    RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
    Long increment = entityIdCounter.getAndIncrement();

    if ((null == increment || increment.longValue() == 0) && liveTime > 0) {//初始设置过期时间
      entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
    }

    return increment;
  }

这样,上面的increment就是自增后的新知值,然后中间通过entityIdCounter.expire(liveTime, TimeUnit.SECONDS);设置过期时间。

当然这里比较讨厌,spring没有在创建RedisAtomicLong对象的时候一起设置过期时间。

可以看看其源码,new RedisAtomicLong最终调用的是这个方法:

private RedisAtomicLong(String redisCounter, RedisConnectionFactory factory, Long initialValue) {
    Assert.hasText(redisCounter, "a valid counter name is required");
    Assert.notNull(factory, "a valid factory is required");

    RedisTemplate<String, Long> redisTemplate = new RedisTemplate<String, Long>();
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new GenericToStringSerializer<Long>(Long.class));
    redisTemplate.setExposeConnection(true);
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.afterPropertiesSet();

    this.key = redisCounter;
    this.generalOps = redisTemplate;
    this.operations = generalOps.opsForValue();

    if (initialValue == null) {
      if (this.operations.get(redisCounter) == null) {
        set(0);
      }
    } else {
      set(initialValue);
    }
  }

可以看到,初始值是0。

然后根进set方法

public void set(long newValue) {
    operations.set(key, newValue);
  }

可以看到,他是采用的operations.set(key, newValue);但是明明还有一个重载的方法void set(K key, V value, long timeout, TimeUnit unit);可以设置过期时间,为啥spring不提供呢。

为了解决这个问题,我们可以自己模拟RedisAtomicLong方法,去实现一个带有过期时间的自增方法。比较简单,读者自行撸代码吧,这里就不写出了。

补充知识:关于spring boot使用redis的increment()方法自增问题

需求是限制IP频繁访问某接口,用的方案是使用redis记录访问IP的值,先设定好初始值,每次访问自增,达到某限定值后,进行阻止。

用的是自定义工具类,使用spring封装的spring-data-redis进行操作,在对某key进行increment()方法时,报错:

redis ERR value is not an integer or out of range

代码逻辑如下:

Integer count = (Integer) redisUtil.get(ipAddress);//取得key的value
    if (count == null){
      redisUtil.set(ipAddress,1,10);
      return false;
    }else if(count == 3){
      return false;
    }else {
      redisUtil.incr(ipAddress,1);
      return false;
    }

第一次进来,如果没有redis中没有数据,则设置key,value和time,key是ip, value初始值为1,有效时长为10秒。

如果没达到限制次数,则对key自增1。

redisUtil.incr()方法实现如下:

@Resource
  private RedisTemplate<String, Object> redisTemplate; //这里使用的是redisTemplate

  public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
    this.redisTemplate = redisTemplate;
  }

  /**
   * 递增
   * @param key 键
//   * @param by 要增加几(大于0)
   * @return
   */
  public long incr(String key, long delta){
    if(delta<0){
      throw new RuntimeException("递增因子必须大于0");
    }
    return redisTemplate.opsForValue().increment(key, delta);
  }

开始以为是incr方法接受的参数是long型,但我传入的是INTEGER类型,但转换后还是没有解决问题,问题不是出在这,后来通过查找资料发现,Spring对Redis序列化的策略有两种,分别是StringRedisTemplate和RedisTemplate,其中StringRedisTemplate用于操作字符串,RedisTemplate使用的是JDK默认的二进制序列化。

大家都知道redis序列化是将key,value值先转换为流的形式,再存储到redis中。

RedisTemplate是使用的JdkSerializationRedisSerializer序列化,序列化后的值包含了对象信息,版本号,类信息等,是一串字符串,所以无法进行数值自增操作。

而StringRedisTemplate序列化策略是字符串的值直接转为字节数组,所以存储到redis中是数值,所以可以进行自增操作。

StringRedisSerializer源码:

public class StringRedisSerializer implements RedisSerializer<String> {
  private final Charset charset;

  public StringRedisSerializer() {
    this(StandardCharsets.UTF_8);
  }

  public StringRedisSerializer(Charset charset) {
    Assert.notNull(charset, "Charset must not be null!");
    this.charset = charset;
  }

  public String deserialize(@Nullable byte[] bytes) {
    return bytes == null ? null : new String(bytes, this.charset);
  }

  public byte[] serialize(@Nullable String string) {
    return string == null ? null : string.getBytes(this.charset); //注意这里是字节数组
  }
}

所以问题出在这里,我们需要自定义序列化策略,在application启动类中添加如下:

 @Bean
  public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
    StringRedisTemplate template = new StringRedisTemplate(factory);
    //定义key序列化方式
    //RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型会出现异常信息;需要我们上面的自定义key生成策略,一般没必要
    //定义value的序列化方式
    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);

    // template.setKeySerializer(redisSerializer);
    template.setValueSerializer(jackson2JsonRedisSerializer);
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    template.afterPropertiesSet();
    return template;

费了2多小时才成功解决问题,RedisUtil.incr()能够成功对key进行自增了,如有错误之处请欢迎指出。

以上这篇利用spring-data-redis实现incr自增的操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Spring Data Elasticsearch使用方式(Elasticsearch)

    Spring Data Elasticsearch使用方式(Elasticsearch)

    这篇文章主要介绍了Spring Data Elasticsearch使用方式(Elasticsearch),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • 如何编写javascript的gulp插件

    如何编写javascript的gulp插件

    本文主要介绍了使用PMD进行代码审查的方法,具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • 如何使用SpringBootCondition更自由地定义条件化配置

    如何使用SpringBootCondition更自由地定义条件化配置

    这篇文章主要介绍了如何使用SpringBootCondition更自由地定义条件化配置,帮助大家更好的理解和学习使用springboot框架,感兴趣的朋友可以了解下
    2021-04-04
  • 解读@RequestBody与post请求的关系

    解读@RequestBody与post请求的关系

    这篇文章主要介绍了解读@RequestBody与post请求的关系,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • Spring Boot集成Swagger接口分类与各元素排序问题

    Spring Boot集成Swagger接口分类与各元素排序问题

    这篇文章主要介绍了Spring Boot集成Swagger接口分类与各元素排序问题,首先我们需要对Swagger中的接口也就是以Controller 层作为第一级梯度进行组织的,Controller在我们实际开发中,与其他具体接口之间是存在一对多的关系,本文给大家介绍的非常详细,需要的朋友参考下吧
    2023-10-10
  • Java解决约瑟夫问题代码实例

    Java解决约瑟夫问题代码实例

    这篇文章主要介绍了Java解决约瑟夫(环)问题的代码实例,决约瑟问题貌似经常出现在面试题中,需要的朋友可以参考下
    2014-03-03
  • Java Socket编程实例(一)- TCP基本使用

    Java Socket编程实例(一)- TCP基本使用

    这篇文章主要讲解Java Socket编程中TCP的基本使用,希望能给大家做一个参考。
    2016-06-06
  • JAVA如何获取工程下的文件

    JAVA如何获取工程下的文件

    这篇文章主要介绍了JAVA如何获取工程下的文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • 解决springboot集成swagger碰到的坑(报404)

    解决springboot集成swagger碰到的坑(报404)

    这篇文章主要介绍了解决springboot集成swagger碰到的坑(报404),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • 一篇文章大家彻底学会Java之格式化输出

    一篇文章大家彻底学会Java之格式化输出

    Java提供了多种格式化输出的方式,包括String.format()、System.out.printf()和Formatter类,这些方法支持数字、日期、字符串等多种格式化操作,这篇文章主要介绍了Java格式化输出的相关资料,需要的朋友可以参考下
    2025-04-04

最新评论