4

For a Java POJO, I want to cache it to Redis using Spring's @Cacheable, @CachePut, and @CacheEvict, however I'd prefer to use Redis' Hash capabilities instead of just serializing the POJO into a String. Essentially, I would like to be able to use something like the ObjectHashMapper against the POJO so that POJO properties are automatically saved as Key/Value pairs in a single Redis entry. This behavior can be seen in the RedisRepository functionality which saves POJOs with the ObjectHashMapper, however, I prefer not to define the cache as a Repository, rather I want to use the Cache annotations.

I have successfully created some custom Spring/Redis configuration that helped me get String serialization working propertly. The Spring documentation for customizing the CacheManager and CacheConfiguration are very "light" and I've not found any relevant examples. I'm not sure whether I need a custom serializer, converter, or some other aspect of the CacheConfiguration. It seems like the serializers are more concerned with the individual keys and values, but I don't see where to configure to catch the entire Object and turn it into a Hash first.

Here is my Redis configuration. It is setup for two caches, "v", and "products", plus defaults to a StringRedisSerializer for other caches.

@Slf4j
@RequiredArgsConstructor
@Configuration
@EnableRedisRepositories(enableKeyspaceEvents=RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP)
public class RedisConfig {

    private final RedisConnectionFactory connectionFactory;

    private static RedisCacheConfiguration createCacheConfiguration(long timeoutInSeconds, RedisSerializationContext.SerializationPair<?> serializationPair) {
        logger.info("Creating CacheConfiguration with timeout of {} seconds", timeoutInSeconds);
        return RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(serializationPair)
                .entryTtl(Duration.ofSeconds(timeoutInSeconds));
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        logger.info("Creating cache manager");
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();

        cacheConfigurations.put("v",createCacheConfiguration(1200, RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())));
        cacheConfigurations.put("products",createCacheConfiguration(-1,RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer())));

        return RedisCacheManager
                .builder(connectionFactory)
                .cacheDefaults(createCacheConfiguration(-1,RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())))
                .withInitialCacheConfigurations(cacheConfigurations)
                .build();
    }
}

Here is an example of how a POJO is serialized per the configuration above:

@Data
@Builder
@AllArgsConstructor
public class ProductSummary implements Serializable {
    @Id
    private String id;
    private String accountId;
    private String name;
    private String roles;
    private String groups;
}

and the way it is serialized:

\xac\xed\x00\x05sr\x004com.foobar.ProductSummaryh\xb3\x9d
\xd4\x0f\xac\xea\xb3\x02\x00\x05L\x00\taccountIdt\x00
\x12Ljava/lang/String;L\x00\x06groupsq\x00~\x00\x01L\x00
\x02idq\x00~\x00\x01L\x00\x04nameq\x00~\x00\x01L\x00
\x05rolesq\x00~\x00\x01xpt\x00\x19acctFv825MKt\x00
\nimwebuserst\x00\x13prod0lwJAWEYt\x00\x11ProductName/2020t\x00\x00

What I'd like is for it to be (in Redis as a Hash):

>HGETALL KEY

 1) "_class"
 2) "com.foobar.cache.CheckoutState"
 3) "accountId"
 4) "ACC000001"
 5) "name"
 6) "lorem ipsum whatever"
 7) "roles"
 8) "role1,role2,role3"
 9) "groups"
10) "groupA,groupB"

with the hash Key being the id.

1 Answers1

1

Guess this isn't that relevant anymore, but maybe somebody else want to do the same, like me.

It isn't that easy, as deep inside the implementation the DefaultRedisCacheWriter

DefaultRedisCacheWriter

uses "SET" - what you basically want is connection.hSet. As I see it the only way to achieve this would be to implement an own RedisCacheWriter.

This might be not enough, as the interface defines: RedisCacheWriter PUT method

which is called by the redis cache manager:

RedisCacher PUT

Long story short, I assume an own "RedisCache" cache class might be the easiest way to go, using just a simple spring data redis "HASH" class or maybe a bit simpler using the RedisTemplate in combination with the spring ObjectHashMapper to created these hashes.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Paul
  • 180
  • 1
  • 8