Spring 引导:Redis CRUD 存储库 findById 或 findAll 始终 returns Optional.empty(空)

Spring Boot: Redis CRUD Repository findById or findAll always returns Optional.empty (null)

大家好,

我正在使用 Spring Boot 2.3.12.RELEASE,它在内部使用 Spring Data Redis 2.3.9.RELEASE 作为托管依赖项。

当我尝试使用 Spring 引导 CRUD 存储库将对象保存到 Redis 缓存时,它被存储而没有任何错误,我可以看到通过 Redis 管理器存储的对象。

但是,当我尝试使用相同的 ID 获取相同的对象时,即使用 CRUD 存储库的 findById() 方法,我无法找到它。

此外,当我在同一个 CRUDRepository 对象上尝试 findAll() 时,我得到了 Optional.empty 结果,这很奇怪,因为 findAll() 应该 return 存储库中存在的所有记录。

我在下面添加了配置、存储库和模型 class 代码以及一些屏幕截图供您阅读。

请注意:我知道在这个平台上有很多与此问题相关的类似问题,我也尝试过这些问题中提到的解决方案,但是没有用对我来说。

这个问题的任何解决方案都会很有帮助。

型号Class:

package com.test.cache.entity;
 
import java.util.concurrent.TimeUnit;
 
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;
import org.springframework.data.redis.core.index.Indexed;
 
import lombok.AllArgsConstructor;
import lombok.Data;
 
@Data
@AllArgsConstructor
@RedisHash("OTPValidationLogCache")
public class OTPValidationLogCache {
 
@Id
@Indexed
private String id;

@Indexed
private int validationFailureCount;
 
@TimeToLive(unit = TimeUnit.MILLISECONDS)
private long expiry;

}

存储库:

package com.test.cache.repository;
 
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
 
import com.test.cache.entity.OTPValidationLogCache;
 
@Repository
public interface OTPValidationLogCacheRepository extends CrudRepository<OTPValidationLogCache, String> {
 
}

Redis配置Class:

package com.test.configuration;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
 
import java.time.Duration;
 
@Configuration
@EnableRedisRepositories(basePackages = "com.test")
public class RedisConfig {
 
public static  final long REDIS_CONNECT_TIMEOUT_SECS = 10L;
 
@Bean
public RedisStandaloneConfiguration redisStandaloneConfiguration() {
final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName("*******");
redisStandaloneConfiguration.setPort(6379);
redisStandaloneConfiguration.setPassword(RedisPassword.of("**********"));
//Credentials hidden for code sharing purpose.
return redisStandaloneConfiguration;
}
 
@Bean
public JedisConnectionFactory redisConnectionFactory() {
final JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder()
.connectTimeout(Duration.ofSeconds(REDIS_CONNECT_TIMEOUT_SECS))
.useSsl()
.build();
 
    return new JedisConnectionFactory(redisStandaloneConfiguration(), jedisClientConfiguration);
}
 
@Bean
public RedisTemplate<String, Object> redisTemplate() {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory());
    return template;
}
}

Redis 管理器截图:

Eclipse IDE - 调试屏幕截图:

好吧,我也向 GitHub 上的 spring-data-redis 存储库提出了同样的缺陷,但该存储库的一位维护者关闭了该缺陷,甚至没有发布任何适当的解决方案。他只是提到了一个现有的问题,这个问题甚至在没有发布任何解决方案的情况下就已经关闭了。这是该问题的 link。

https://github.com/spring-projects/spring-data-redis/issues/2130

因此,在做一些研究时,我发现了一个解决方案,我在这里分享它对我的情况有效。

解决方案不是使用 Spring Boot 实现的默认 CRUD 存储库方法,而是编写您自己的存储库 class 具有符合您的标准的方法来存储和从 Redis 获取数据缓存。就是这样,现在您应该能够 store/fetch 在整个项目中使用存储库方法获取数据。

我在下面发布一个示例以供参考。

自定义存储库界面

package com.test.cache.repository;

import java.io.IOException;
import java.util.Map;

import com.test.cache.entity.OTPValidationLogCache;

public interface OTPValidationLogCacheRepository {

    void save(OTPValidationLogCache customer);
    OTPValidationLogCache find(Long id);
    Map<?,?> findAll() throws IOException;
    void update(OTPValidationLogCache customer);
    void delete(Long id);
}

自定义存储库接口实现

package com.test.cache.repository;

import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ScanOptions.ScanOptionsBuilder;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.test.cache.entity.OTPValidationLogCache;
import com.test.configuration.AppConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;

@Repository
public class OTPValidationLogCacheRepositoryImpl implements OTPValidationLogCacheRepository {
    
    private String key;
    
    private RedisTemplate redisTemplate;
    private HashOperations hashOperations;
    private ObjectMapper objMapper;
    
    @Autowired
    public OTPValidationLogCacheRepositoryImpl(RedisTemplate redisTemplate, ObjectMapper objmapper) {
        this.redisTemplate = redisTemplate;
        this.objMapper = objmapper;
    }
    
    @PostConstruct
    private void init() {
        hashOperations = redisTemplate.opsForHash();
    }
    
    @Override
    public void save(OTPValidationLogCache otpvalCache) {
        hashOperations.put(key.concat(otpvalCache.getId().toString()), otpvalCache.getId(), otpvalCache);
        setExpiryTime(key.concat(String.valueOf(otpvalCache.getId())), AppConfig.getUserBanDurationInSeconds());
    }

    @Override
    public OTPValidationLogCache find(Long id) {
        return (OTPValidationLogCache) hashOperations.get(key.concat(String.valueOf(id)), id);
    }

    @Override
    public Map findAll() throws IOException {
        Map<Integer, OTPValidationLogCache> values = Maps.newHashMap();
        Cursor c =  hashOperations.scan(OTPValidationLogCache.class, new ScanOptionsBuilder().match(key.concat("*")).build());
        AtomicInteger count = new AtomicInteger(1);
        c.forEachRemaining(element ->
                {
                    values.put(count.getAndIncrement(), objMapper.convertValue(element, OTPValidationLogCache.class));
                }
                );
        c.close();
        return values;
    }

    @Override
    public void update(OTPValidationLogCache customer) {
        hashOperations.put(key, customer.getId(), customer);
    }

    @Override
    public void delete(Long id) {
        hashOperations.delete(key, id);
    }
    
    private void setExpiryTime(String key, Long timeout)
    {
        redisTemplate.expire(key, Duration.ofSeconds(timeout));
    }

    public synchronized void setKey(String key)
    {
        this.key = key;
    }
}

希望这对以后可能遇到此问题的其他人有所帮助。

此外,对于这个问题还有一个替代方案,那就是切换到不同的库提供者,例如 Redisson,但是,我还没有尝试过,所以如果你愿意,你可以试试看。

您的实体需要相同的包, 我通过提取一个库并将我的实体放在那里解决了这个问题

你会在这里找到解释: https://github.com/spring-projects/spring-data-redis/issues/2114