spring-data-redis icon indicating copy to clipboard operation
spring-data-redis copied to clipboard

Redis repository: Error while converting to Timestamp

Open soheilrahsaz opened this issue 1 year ago • 3 comments

Using Spring data Redis 3.2.5 and repository, Given this structure

@RedisHash("Fruit")
public class Fruit {
    @Id
    private Integer id;
    private String name;
    private Timestamp createdAt;
}

And

public interface FruitRedisRepository extends CrudRepository<Fruit, Integer> {}
@Configuration
public class RedisConfig {
    @Bean
    public RedisConnectionFactory redisConnectionFactory()
    {
        return new LettuceConnectionFactory("localhost", 16379);
    }
}

This test fails and throws exception:

@SpringBootTest
class TestSpringRedisApplicationTests {

    @Autowired
    FruitRedisRepository fruitRedisRepository;

    @Test
    void test() {
        //creating sample fruit
        Fruit fruit = new Fruit(1, "banana", Timestamp.from(Instant.now()));

        //fruit is saved into redis with no problem
        assertThat(fruitRedisRepository.save(fruit)).isNotNull();

        //this should work fine but throws exception because of the timestamp
        assertThat(fruitRedisRepository.findById(1)).isNotNull();
    }
}

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [byte[]] to type [java.sql.Timestamp]

at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:294) at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:185) at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:165) at org.springframework.data.redis.core.convert.MappingRedisConverter.fromBytes(MappingRedisConverter.java:998) at org.springframework.data.redis.core.convert.MappingRedisConverter.readProperty(MappingRedisConverter.java:329) at org.springframework.data.redis.core.convert.MappingRedisConverter.lambda$doReadInternal$0(MappingRedisConverter.java:245) at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:298) at org.springframework.data.redis.core.convert.MappingRedisConverter.doReadInternal(MappingRedisConverter.java:237) at org.springframework.data.redis.core.convert.MappingRedisConverter.read(MappingRedisConverter.java:183) at org.springframework.data.redis.core.convert.MappingRedisConverter.read(MappingRedisConverter.java:114) at org.springframework.data.redis.core.RedisKeyValueAdapter.get(RedisKeyValueAdapter.java:290) at org.springframework.data.keyvalue.core.KeyValueTemplate.lambda$findById$3(KeyValueTemplate.java:241) at org.springframework.data.keyvalue.core.KeyValueTemplate.execute(KeyValueTemplate.java:314) at org.springframework.data.keyvalue.core.KeyValueTemplate.findById(KeyValueTemplate.java:239) at org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository.findById(SimpleKeyValueRepository.java:98) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354) at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277) at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516) at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285) at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) at jdk.proxy2/jdk.proxy2.$Proxy59.findById(Unknown Source) at ir.co.rrr.testspringredis.TestSpringRedisApplicationTests.test(TestSpringRedisApplicationTests.java:30) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

But the timestamp is saved correctly in redis: hmget Fruit:1 id name createdAt

  1. "1"
  2. "banana"
  3. "1713704899797"

soheilrahsaz avatar Apr 21 '24 13:04 soheilrahsaz

Have you tried registering a custom Converter<byte[], Timestamp>? We don't support the SQL timestamp as Redis isn't SQL.

mp911de avatar Apr 23 '24 07:04 mp911de

I was mistaking that because it did convert Timestamp to byte[], then it should do the inverse conversion as well. (The conversion from Timestamp to byte[] was because of a BinaryConverter from java.util.date to byte[])

By following documentations, I created a Converter<byte[], Timestamp> and registered it using RedisCustomConversions, but still it throws the same exception and I couldn't find out what am I doing wrong here. Could you give me a hint on this?

@ReadingConverter
@Component
public class CustomReadingTimestampConverter implements Converter<byte[], Timestamp> {
    @Override
    public Timestamp convert(byte @NonNull [] source) {
        return new Timestamp(Long.parseLong(new String(source)));
    }
}
@Configuration
public class RedisConfig {

    @Bean
    public RedisCustomConversions customConversions(CustomReadingTimestampConverter customTimestampConverter){
        return new RedisCustomConversions(List.of(customTimestampConverter));
    }

soheilrahsaz avatar Apr 23 '24 11:04 soheilrahsaz

You must register a bean with the name redisCustomConversions like this:

@Bean
public RedisCustomConversions redisCustomConversions(CustomReadingTimestampConverter customTimestampConverter){
    return new RedisCustomConversions(List.of(customTimestampConverter));
}

Please add to the documentation that the bean must match by name

SergeyBasharkin avatar Sep 02 '24 13:09 SergeyBasharkin