Redis: class java.lang.String cannot be cast to class java.util.List in #Cacheable method - spring

I need to add a Redis cache in a method that returns a list of values.
I'm using this tutorial as basis https://www.baeldung.com/spring-data-redis-tutorial
The exception shows this
java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.List (java.lang.String and java.util.List are in module java.base of loader 'bootstrap')
at
#Cacheable(cacheNames = "customerDetailByParam", key="{#searchParams.toString()}")
#Retryable(value = { HttpServerErrorException.class }, maxAttempts = RETRY_ATTEMPTS, backoff = #Backoff(delay = 5000))
public List<ObjectResponse> searchCustomerDetailByParam(MultiValueMap<String, String> searchParams)
I've been looking for some solutions, however, none seems to work.
CacheConfig.java
#Configuration
#EnableCaching
#ConditionalOnMissingBean(value = CacheManager.class)
#Slf4j
public class CacheConfig {
#Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost", 6379);
//redisStandaloneConfiguration.setPassword(RedisPassword.of("yourRedisPasswordIfAny"));
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(jedisConnectionFactory());
template.setValueSerializer(new GenericToStringSerializer<Object>(Object.class));
template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer(getClass().getClassLoader());
template.setDefaultSerializer(serializer);
return template;
}
}
ObjectResponse.java
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ObjectResponse implements Serializable {
#JsonProperty("id")
private String customerId;
#JsonProperty("name")
#JsonAlias("full_name")
private String customerName;
private String document;
private String email;
}

I was able to fix the problem changing the template to the following configuration.
#Bean
public RedisTemplate<String, Object> redisTemplate() {
final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(jedisConnectionFactory());
return template;
}

Related

How to use Redis template in an async annotated method in spring boot

I am trying to use hash operations of Redis from an async (performTask) method, but I keep getting this exception,
java.lang.IllegalStateException: JedisConnectionFactory was destroyed and cannot be used anymore
this async method is called from another service class in a loop like shown in the below code snippet of service class,
Service class:
....
for(CacheJob job : jobs.getJobs()) {
if(job.getCacheKey() != null && job.getLupdTSSQL() != null) {
asyncService.performTask(job.getCacheKey(), job.getLupdTSSQL());
}
}
....
Async service class:
#Autowired
private HashOperations<String, String, String> lupdTSHashOperations;
#Async
public void performTask(String cacheKey, String sql) throws Exception {
String lupdTSFromCacheStr = null;
System.out.println("Execute method asynchronously. Thread Id"
+ Thread.currentThread().getId()+": cacheKey :"+cacheKey);
....
String lupdTSCacheKey = null;
...
try {
lupdTSCacheKey = cacheKey + Constants.SUFFIX_LUPDTS;
lupdTSFromCacheStr = lupdTSHashOperations.get(cacheKey, lupdTSCacheKey);
....
.....
AsyncConfig
#Configuration
#EnableAsync
public class MyAsyncConfig implements AsyncConfigurer{
#Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("my-cache-jobs-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
.....
Redis config
#Configuration
public class MyCacheRedisConfig {
#Value("${spring.redis.host}")
private String redisHost;
#Value("${spring.redis.port}")
private String redisPort;
#Value("${spring.redis.password}")
private String redisPassword;
#SuppressWarnings("deprecation")
#Bean(name = "jedisConnectionFactory")
JedisConnectionFactory jedisConnectionFactory() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
poolConfig.setMinIdle(0);
JedisConnectionFactory rsc = new JedisConnectionFactory();
rsc.setPoolConfig(poolConfig);
rsc.setHostName(redisHost);
rsc.setPort(Integer.parseInt(redisPort));
rsc.setPassword(redisPassword);
rsc.setUseSsl(true);
return rsc;
}
.....
....
#Bean(name = "lupdTSRedisTemplate")
public RedisTemplate<String, String> lupdTSRedisTemplate() {
final RedisTemplate<String, String> template = new RedisTemplate<String, String>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
#Bean(name="lupdTSHashOperations")
public HashOperations<String, String, String> lupdTSHashOperations(
#Qualifier("lupdTSRedisTemplate") RedisTemplate<String, String> lupdTSRedisTemplate) {
return lupdTSRedisTemplate.opsForHash();
}
this exception is only occurring when I try to use the Redis hash operations from async method

In springboot-redis project,error ClassCastException is reported

The stored objects are nested.
I don't understand why there are problems.
I want him to be inverted correctly.
Who can help me?
RedisConfig:
#Configuration
public class RedisConfig {
#Bean
#SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
Method with error:
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
The error info:
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.maple.webserver.security.SecurityUserDetails (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.maple.webserver.security.SecurityUserDetails is in unnamed module of loader 'app')

RedisTemplate nullException

There was a problem when I used Springboot to integrate Redis. I want to customize a RedisTemplate, and when I use it, I find that it is always empty and cannot be injected. My code is as follows:
#Configuration
public class RedisConfig {
#Bean(name = "myRedisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
#Autowired
#Qualifier(value = "myRedisTemplate")
private RedisTemplate<String, Object> redisTemplate;
redisTemplate is empty in debug mode.I don't know what went wrong
It could be caused by jedisConnectionFactory do you have such a Bean
#Bean
JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory();
}

Spring boot Redis get null in another project

I have two projects that use Spring boot with Redis. In the first project, I have saved data into Redis cache.
Then, in the second project, when I get data from Redis that I saved in project one it is always null, and project one is also null when I try to get data from Redis that I have saved in project two.
Both projects use the dto as below.
#Data
#EqualsAndHashCode(callSuper = false)
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(content = Include.NON_NULL)
public class AccountDto implements Serializable {
#JsonIgnore
private static final long serialVersionUID = -2006838697507278668L;
#JsonProperty("account_no")
private String accountNo;
#JsonProperty("account_type")
private String accountType;
#JsonProperty("customer_no")
private String customerNO;
#JsonProperty("entity_code")
private String entityCode;
#JsonProperty("auth_stat")
private String authStat;
#JsonProperty("create_at")
#Temporal(TemporalType.TIMESTAMP)
private Date createAt;
}
Redis configuration
#Configuration
#RefreshScope
#EnableRedisRepositories
public class RedisConfiguration {
#Value("${spring.redis.host: ''}")
private String redisHost;
#Value("${spring.redis.port: ''}")
private int redisPort;
#Bean
public JedisConnectionFactory connectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(redisHost);
configuration.setPort(redisPort);
return new JedisConnectionFactory(configuration);
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}
This class I have created two methods, one for saving and getting data from Redis
#RequiredArgsConstructor
#Service
public class AccountDtoCache {
private static final String HASH_KEY_ACCOUNT = "Account";
private static HashOperations<String, String, AccountDto> hashOperations;
private final RedisTemplate<String, Object> redisTemplate;
#PostConstruct
private synchronized void init() {
hashOperations = redisTemplate.opsForHash();
}
public void save(AccountDto account) {
hashOperations.put(HASH_KEY_ACCOUNT, account.getAccountNo(), account);
}
public static AccountDto getByAccountNo(String account) {
return hashOperations.get(HASH_KEY_ACCOUNT, account);
}
}
I try to get data from Redis as below.
When I debug, it's always null in other projects, but it's working with the same project that I have saved data into Redis.
AccountDtoCache.getByAccountNo("accountNo");
I fixed the issue above with two steps as below
I changed both of the projects package names to the same package name
Redis config
#Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}

Cache with redis cache manager, redisTemplate and multiple serializers

I need to cache multiple types like:
public Country findCountry(String countryName)
and:
public List<Destination> findAllDestinations(String countryName)
I am using RedisCacheManager and RedisTemplate only support only one serializer.
It is solved now after some research.
change spring-data-redis to 1.4.2.RELEASE
extend RedisCacheManager with your class with cache map to serializer (cacheName->serializer) and caches names
overrides the getCache method(Cache getCache(String name)) and based on cache name, set the serializer name in the redis template
use your customized cache manager
Example -
public class CustomRedisCacheManager extends RedisCacheManager
{
public static final String CACHE_NAME_DEFAULT = "default";
public static final String CACHE_NAME_COUNTRY = "country";
public static final String CACHE_NAME_DESTINATIONS = "destinations";
private Map<String, RedisCache> redisCaches = new HashMap<>();
public CustomRedisCacheManager(Map<String, RedisTemplate> redisTemplates)
{
super(redisTemplates.get(CACHE_NAME_DEFAULT), redisTemplates.keySet());
redisTemplates.keySet().stream().forEach(cacheName -> redisCaches.put(cacheName, new RedisCache(cacheName, null, redisTemplates.get(cacheName), 0)));
}
#Override
public Cache getCache(String cacheName)
{
return redisCaches.get(cacheName);
}
}
#Configuration
#EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport
{
#Bean
public JedisConnectionFactory jedisConnectionFactory()
{
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisHostName);
factory.setPort(redisPort);
factory.setTimeout(100);
return factory;
}
#Bean
public CacheManager cacheManager()
{
Map<String, RedisTemplate> templates = new HashMap<>();
templates.put(CACHE_NAME_DEFAULT, getDefaultRedisTemplate());
templates.put(CACHE_NAME_COUNTRY, getMetadataRedisTemplate());
templates.put(CACHE_NAME_DESTINATIONS, getDestinationsRedisTemplate());
SabreRedisCacheManager sabreRedisCacheManager = new SabreRedisCacheManager(templates);
return sabreRedisCacheManager;
}
#Bean
public RedisTemplate<Object, Object> getDefaultRedisTemplate()
{
return getBaseRedisTemplate();
}
#Bean
public RedisTemplate<Object, Object> getCountryRedisTemplate()
{
RedisTemplate<Object, Object> redisTemplate = getBaseRedisTemplate();
redisTemplate.setValueSerializer(jsonRedisSerializer(Country.class));
return redisTemplate;
}
#Bean
public RedisTemplate<Object, Object> getDestinationsRedisTemplate()
{
RedisTemplate<Object, Object> redisTemplate = getBaseRedisTemplate();
redisTemplate.setValueSerializer(jsonRedisSerializer(TypeFactory.defaultInstance().constructCollectionType(List.class, Destination.class)));
return redisTemplate;
}
private RedisTemplate<Object, Object> getBaseRedisTemplate()
{
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
redisTemplate.setKeySerializer(stringRedisSerializer());
redisTemplate.setHashKeySerializer(stringRedisSerializer());
redisTemplate.setValueSerializer(jsonRedisSerializer(Object.class));
return redisTemplate;
}
private Jackson2JsonRedisSerializer jsonRedisSerializer(Class type)
{
return jsonRedisSerializer(TypeFactory.defaultInstance().constructType(type));
}
private Jackson2JsonRedisSerializer jsonRedisSerializer(JavaType javaType)
{
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(javaType);
jackson2JsonRedisSerializer.setObjectMapper(new JsonObjectMapper());
return jackson2JsonRedisSerializer;
}
}

Resources