利用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自增的操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • springboot配置redis过程详解

    在springboot中,默认继承好了一套完好的redis包,可以直接使用,但是如果使用中出了错不容易找到错误的原因,因此这里使用自己配置的redis: 需要使用的三个主要jar包: <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>

  • SpringBoot整合Redis的步骤

    1.添加配置文件: Redis.properties # 配置单台redis服务器 redis.host=192.168.126.129 ip地址 redis.port=6379 端口号 设置配置类: RedisConfig 2.将对象转化为Json格式入门案例 API: MAPPER.writeValueAsString(itemDesc); 3.将对象转化为Json格式格式优化 4.AOP实现Redis缓存 AOP实现Redis @cacheFind()注解 实现策略: A.需要自定义注解C

  • 使用redis的increment()方法实现计数器功能案例

    一直知道redis可以用来实现计数器功能,但是之前没有实际使用过,昨天碰到一个需求:用户扫码当天达到20次即提示:当日扫码次数达到上限! 当时就想到使用redis的递增方法increment()来实现计数器功能,一定要注意redisTemplate和stringRedisTemplate的使用 首先设置key: 该key我使用了用户id和当天日期作为key的一部分,date:xxxx-xx-xx格式,这样一来该用户在第二天扫码的时候又是一个新key,因为日期不同了 设置key的过期时间: 实现计

  • 使用Redis incr解决并发问题的操作

    项目背景: 1.新增问题件工单,工单中有工单编码字段,工单编码字段的规则为 "WT"+yyyyMMdd+0000001. 2.每天的工单生成量是30W,所以会存在并发问题 解决思路: 1.首先乐观的认为redis不会宕机,对应的缓存不会被清除(除非人为操作,人为操作会有独立的补救办法) 2.将工单编码存到缓存中(redis),其值只存"WT"+yyyyMMdd后面的数字部分: 对应的key为:key标识+yyyyMMdd,即每天一个key 3.每次生成工单编码时,先

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

    应该有不少人在使用spring-data-redis时遇到各种各样的问题.反正我是遇到了. 由于是隔了一段时间才写的本篇博客,也懒得去重现哪些错误场景了,下面凭着记忆写了几个我遇到的问题: redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range 使用的RedisTemplate,做读写操作时候,都是要经过序列化和反序列化. 这时你使用redisTemplate.o

  • 浅谈Spring Data Redis读不到设进去的值

    目录 目标 1 注意读.取一致性 解析 AbstractOperations#rawKey 修正 目标 精通 Spring Data Redis 操作流程. Spring Data提供了对市场上主流数据库支持: Spring Data Commons Spring Data JPA Spring Data KeyValue Spring Data LDAP Spring Data MongoDB Spring Data Redis Spring Data REST Spring Data for

  • 利用Spring Data MongoDB持久化文档数据的方法教程

    前言 本文主要给大家介绍了关于利用Spring Data MongoDB持久化文档数据的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 介绍 NoSQL:not only SQL,非关系型数据 MongoDB是文档型数据,文档是独立的实体,文档数据库不适用于关联关系明显的数据 Spring Data MongoDB 1.Spring Data MongoDB提供了三种方式在Spring应用中使用MongoDB 通过注解实现对象-文档映射 使用MongoTemplate

  • 使用Spring Data Redis实现数据缓存的方法

    引言 目前很多系统为了解决数据读写的性能瓶颈,在系统架构设计中使用Redis实现缓存,Spring框架为了让开发人员更加方便快捷的使用Redis实现缓存,对Redis的操作进行了包装. 0.缓存 个人理解的缓存是指用于存储频繁使用的数据的空间,关注点是存储数据的空间和使用频繁的数据.缓存技术,简单的说就是先从缓存中查询数据是否存在,存在则直接返回,不存在再执行相应的操作获取数据,并将获取的数据存储到缓存中,它是一种提升系统性能的重要方法. 1.Redis Redis是一个开源的.内存存储key-

  • 解决spring data redis的那些坑

    目录 spring data redis的那些坑 1. 使用lua脚本,返回类型解析错误 2. spring redis基于lettuce配置Client必须显示调用 spring data redis 的优缺点 spring data redis的那些坑 spring 的IOC很少有bug,AOPbug开始多起来,到了它的一些"玩具"一样的组件,bug无处不在.而且跟一般的开源框架不同,在github上你报告issue,会被"这不是一个bug"强行关闭.开一博文记

  • Spring Data JPA例子代码[基于Spring Boot、Mysql]

    关于Spring Data Spring社区的一个顶级工程,主要用于简化数据(关系型&非关系型)访问,如果我们使用Spring Data来开发程序的话,那么可以省去很多低级别的数据访问操作,如编写数据查询语句.DAO类等,我们仅需要编写一些抽象接口并定义相关操作即可,Spring会在运行期间的时候创建代理实例来实现我们接口中定义的操作. 关于Spring Data子项目 Spring Data拥有很多子项目,除了Spring Data Jpa外,还有如下子项目. Spring Data Comm

  • Spring Data MongoDB中实现自定义级联的方法详解

    前言 Spring Data MongoDB 项目提供与MongoDB文档数据库的集成,Spring与Hibernate集成时,Spring提供了org.springframework.orm.hibernate3.HibernateTemplate实现了对数据的CRUD操作, Spring Data MongoDB提供了org.springframework.data.mongodb.core.MongoTemplate对MongoDB的CRUD的操作,包括对集成的对象映射文件和POJO之间的

  • spring使用redis操作key-value的示例代码

    连接到 Redis Redis 连接工厂会生成到 Redis 数据库服务器的连接.Spring Data Redis 为四种 Redis 客户端实现提供了连接工厂: JedisConnectionFactory JredisConnectionFactory LettuceConnectionFactory SrpConnectionFactory 具体选择哪一个取决于你.我建议你自行测试并建立基准,进而确定哪一种 Redis 客户端和连接工厂最适合你的需求.从 Spring Data Redi

  • Springboot使用Spring Data JPA实现数据库操作

    SpringBoot整合JPA 使用数据库是开发基本应用的基础,借助于开发框架,我们已经不用编写原始的访问数据库的代码,也不用调用JDBC(Java Data Base Connectivity)或者连接池等诸如此类的被称作底层的代码,我们将从更高的层次上访问数据库,这在Springboot中更是如此,本章我们将详细介绍在Springboot中使用 Spring Data JPA 来实现对数据库的操作. JPA & Spring Data JPA JPA是Java Persistence API

  • 关于Spring Data Jpa 自定义方法实现问题

    目录 Spring Data Jpa 自定义方法的实现 自己的接口 主接口 我新建一个类来实现我自己的接口 Spring Data Jpa自定义方法关键字 Spring Data Jpa 自定义方法的实现 最近项目中用到了Spring Data JPA,在里面我继承了一个PagingAndSortingRepository的接口,期望的是利用Spring Data JPA提供的便利. 同时我也希望自己有一个能定义自己方法的接口,因为单纯靠Spring Data JPA中提供的功能还是有很多业务逻

随机推荐