(原创)Java使用RedisTemplate操作Redis的那些坑

本文探讨了Spring Data Redis中RedisTemplate默认序列化导致的key乱码问题及解决方案,介绍了如何自定义RedisTemplate使用GenericJackson2JsonRedisSerializer进行序列化,并讨论了StringRedisTemplate和自定义RedisTemplate在数据读取上的差异。

坑一、

    方法:

    直接使用注入的方式       

@AutoWired
private RedisTemplate redisTemplate;

    异常现象:

        key会出现乱码

127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04name"

 

原因:

spring-data-redis的RedisTemplate<K, V>模板类在操作redis时默认使用JdkSerializationRedisSerializer来进行序列化

解决:

自定义RestTemplate,使用GenericJackson2JsonRedisSerializer解决序列化问题,注入的@AutoWired private RedisTemplate redisTemplate;其实是自定义的RestTemplate,

注意事项:@Bean注入的bean是方法名,所以要目方法名写成restTemplate,要么写自定义的(如restTemplateInit()),在注入的时候就要用@Resources(name="restTemplateInit")

package com.example.demo.config;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

@Data
@Component
@Configuration
@Slf4j
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

//        // 使用Jackson2JsonRedisSerialize 替换默认序列化
//        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//
//        ObjectMapper objectMapper = new ObjectMapper();
//        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//
//        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);

        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);

        redisTemplate.setDefaultSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setEnableDefaultSerializer(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

}

坑二、用StringRedisTemplate和RedisTemplate取值赋值的问题

redisTemplate.opsForValue().set("test1", "123");
redisTemplate.opsForValue().set("test2", ayUser);
//stringRedisTemplate.opsForValue().set("test3", "{\"name\":\"jack\"}");
//value设置成{\"name\":\"jack\"}这样的json也没用,需要带上序列化的类,像下面这样
stringRedisTemplate.opsForValue().set("test3", "{\"@class\":\"com.example.demo.model.AyUser\",\"id\":null,\"name\":\"shenzhen\",\"password\":null}");

System.out.println((String) redisTemplate.opsForValue().get("test1"));//123
System.out.println(stringRedisTemplate.opsForValue().get("test1"));//"123"
System.out.println((AyUser) redisTemplate.opsForValue().get("test2"));//AyUser(id=null, name=shenzhen, password=null)
System.out.println(redisTemplate.opsForValue().get("test3"));//AyUser(id=null, name=shenzhen, password=null)
System.out.println(stringRedisTemplate.opsForValue().get("test3"));//{"@class":"com.example.demo.model.AyUser","id":null,"name":"shenzhen","password":null}

使用上面注入的自定义的RedisTemplate都是可以反序列化成功的,如下!但是使用的stringRedisTemplate虽然set的是返祖json格式的且带序列化的,但是不能直接通过强转实现(AyUser),需要通过com.alibaba.fastjson.JSON去反序列化

AyUser ayUser_3_0 = JSON.parseObject(stringRedisTemplate.opsForValue().get("test3_0"), AyUser.class);

通过ayUser_3_0.getName()就可以取到。

AyUser ayUser_2 = (AyUser) redisTemplate.opsForValue().get("test2");//ayUser_2.getName()
AyUser ayUser_3 = (AyUser) redisTemplate.opsForValue().get("test3");//ayUser_3.getName()
<think>好的,我现在需要帮助用户解决Redis的evalsha命令中出现的“ERR value is not an integer or out of range”错误。首先,我得回忆一下这个错误通常是什么原因引起的。根据之前的引用内容,用户之前的问题是关于RedisTemplate的increment操作报同样的错误,主要是因为序列化配置不正确导致的值类型问题。现在用户的问题转向了evalsha命令,这可能涉及到Lua脚本的执行,所以需要结合Lua脚本和Redis的数据类型来分析。 首先,evalsha命令是用于执行预先加载到Redis中的Lua脚本的SHA1摘要。这个错误信息表明,在脚本执行过程中,某个操作期望得到一个整数,但实际得到的值不是整数或者超出了范围。可能的原因有几个: 1. **数据类型不匹配**:在Lua脚本中,可能对某个键进行了自增或自减操作,比如使用INCR或DECR命令,但该键存储的值不是整数类型。例如,如果键的值是字符串或浮点数,执行这些命令就会报错。 2. **序列化问题**:如果用户使用的是RedisTemplate,并且没有正确配置序列化方式,可能导致存储的值被序列化为非整数格式。例如,使用JSON序列化可能会将数字存储为字符串,导致在Lua脚本中操作时无法识别为整数。 3. **值超出范围**:即使值是整数,如果进行自增或自减后超出了Redis支持的整数范围(64位有符号整数),也会导致错误。 接下来,我需要参考用户提供的引用内容,特别是关于序列化配置的解决方案。之前的解决方案是通过配置RedisTemplate的序列化器,将key和value分别用String和合适的序列化方式处理,比如Jackson2JsonRedisSerializer或GenericToStringSerializer。这可能同样适用于evalsha的情况,因为如果Lua脚本操作的键的值在存储时被错误地序列化,就会导致类型不匹配。 另外,用户提到evalsha命令,可能需要检查Lua脚本本身是否存在问题。例如,脚本中的变量是否传递了正确的数值类型,或者在操作之前是否没有检查值是否存在或是否为整数。例如,如果脚本中使用了类似INCRBY命令,但传递的参数是字符串,或者从Redis中获取的值不是整数,就会报错。 可能的解决步骤包括: - 检查Lua脚本中涉及数值操作的命令,确认所有操作数都是整数。 - 确保通过客户端(如RedisTemplate)传递的参数类型正确,没有被错误序列化。 - 配置RedisTemplate的序列化方式,确保数值类型正确存储为整数。 - 在Lua脚本中添加类型检查,例如使用Redis的TYPE命令来验证键的数据类型,或者使用Lua的tonumber函数进行转换。 还需要考虑用户的具体使用场景。比如,他们可能在脚本中使用了INCR,而对应的键之前存储的是通过不同序列化方式写入的值,导致类型不兼容。因此,统一序列化配置是关键,类似于之前解决increment错误的方案,需要确保所有写入操作使用正确的序列化器。 最后,需要总结这些可能的原因,并提供对应的解决方案,特别是配置序列化器的方法,以及检查Lua脚本中的类型处理,帮助用户彻底解决这个问题。</think>关于Redis的`EVALSHA`命令出现`ERR value is not an integer or out of range`错误,其核心原因与**数据类型不匹配**和**序列化配置**密切相关。以下是具体分析与解决方案: --- ### **一、错误原因分析** #### 1. **Lua脚本中的数值操作不兼容** 在Lua脚本中若执行`INCR`/`DECR`或位操作(如`BITFIELD`)时,若操作的键值不是整数类型(如字符串、浮点数),会直接触发此错误。例如: ```lua -- 假设键"counter"存储的是字符串"abc" redis.call("INCR", KEYS[1]) -- 报错:无法对非整数进行自增 ``` #### 2. **序列化配置问题** 使用`RedisTemplate`时,若未正确配置序列化器,可能导致数值被存储为其他格式(如JSON字符串)。例如: - 默认的`JdkSerializationRedisSerializer`会将整数序列化为二进制格式,而非Redis原生整数。 - 若使用`GenericToStringSerializer`但未正确处理类型转换,也会导致数据格式异常[^2][^3]。 #### 3. **值超出整数范围** Redis支持的整数范围为64位有符号整数($-2^{63}$至$2^{63}-1$)。若脚本操作导致数值越界,也会触发该错误。 --- ### **二、解决方案** #### 1. **检查并修复Lua脚本** - **显式类型转换**:在Lua脚本中使用`tonumber()`函数确保操作数为整数。 ```lua local current = tonumber(redis.call("GET", KEYS[1])) or 0 redis.call("SET", KEYS[1], current + 1) ``` - **验证数据类型**:通过`TYPE`命令检查键的类型: ```lua if redis.call("TYPE", KEYS[1])["ok"] ~= "string" then return redis.error_reply("Invalid type") end ``` #### 2. **配置RedisTemplate序列化器** 在Spring Boot中,需显式配置`RedisTemplate`的序列化方式,确保数值以整数格式存储: ```java @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); // 使用StringRedisSerializer存储数值为字符串,或自定义序列化器 template.setValueSerializer(new GenericToStringSerializer<>(Long.class)); template.afterPropertiesSet(); return template; } ``` 此配置将数值序列化为字符串(Redis可自动识别为整数),避免二进制或JSON格式的干扰[^2][^3]。 #### 3. **重置异常数据** 若已有错误数据,需手动修复: ```bash # 将键"counter"的值重置为整数 127.0.0.1:6379> SET counter 0 ``` --- ### **三、验证步骤** 1. **检查键值类型**: ```bash TYPE your_key # 应为"string" OBJECT ENCODING your_key # 应为"int"或"raw"(数值字符串) ``` 2. **测试Lua脚本**: ```bash EVAL "return redis.call('TYPE', KEYS[1])" 1 your_key ``` --- ### **相关问题** 1. Redis的Lua脚本如何保证原子性? 2. 如何避免RedisTemplate序列化导致的类型错误? 3. Redis支持哪些数据类型的原子操作? --- 通过以上方法,可系统性解决`EVALSHA`命令中的数据类型错误问题。若涉及复杂操作,建议在脚本中增加类型校验逻辑,并统一客户端的序列化配置[^1][^2]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值