Null support cache in Spring Data Redis

Using Spring Date Redis in a Spring project makes it easy to cache to Redis.

However, one of the difficulties with caching is whether to cache ** null things **. If the one you took the time to try to get was null (if it wasn't), you want to remember that it was null.

Previously, null was not cached when using Spring Date Redis. (Of course, it can be handled by customizing the serializer / deserializer) This has been quite annoying until now, but it has been officially supported since version 1.8.

Support caching null values via RedisCache https://jira.spring.io/browse/DATAREDIS-553

All you have to do is set the RedisCacheManager constructor argument to flag it for null support. Below is a sample.

Application.java


@EnableCaching
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Autowired
    private JedisConnectionFactory jedisConnectionFactory;

    @Bean
    public RedisTemplate<Object, Object> redisTemplate() {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        redisTemplate.setKeySerializer(new GenericJackson2JsonRedisSerializer(""));
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(""));
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager() {
        List<String> cacheNames = Arrays.asList("person");
        RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate(), cacheNames, true); //Finally, cache Null as an argument.

        //Setting cache key prefix
        redisCacheManager.setUsePrefix(true);
        redisCacheManager.setCachePrefix(new RedisCachePrefix() {
            @Override
            public byte[] prefix(String cacheName) {
                return ("SAMPLE:" + cacheName + ":").getBytes(StandardCharsets.UTF_8);
            }
        });

        return redisCacheManager;
    }
}

Person.java


@Data
@AllArgsConstructor
public class Person {
    private int id;
    private String name;
}

PersonService.java


@Slf4j
@Service
public class PersonService {

    @Cacheable("person")
    public Person findById(int id) {
        log.info("called findById. id = {}", id);

        try {
            TimeUnit.SECONDS.sleep(id);
        } catch (InterruptedException e) {
            // ignore
        }

        return id >= 10 ? null : new Person(id, "Hogetaro" + id);
    }
}

PersonServiceTest.java


@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class PersonServiceTest {

    @Autowired
    PersonService target;
    @Autowired
    CacheManager cacheManager;

    @Before
    public void setup() {
        Cache cache = cacheManager.getCache("person");
        cache.clear();
    }

    @Test
    public void testFindByIdNull() {
        StopWatch stopWatch = new StopWatch();

        stopWatch.start();
        Person p1 = target.findById(10); //Takes 10s
        stopWatch.stop();
        log.info("result = {}, elapsed = {}", p1, stopWatch.getLastTaskTimeMillis());
        assertNull(p1);
        assertTrue(stopWatch.getLastTaskTimeMillis() > 10000);

        stopWatch.start();
        Person p2 = target.findById(10); //The second time the cache works and is fast
        stopWatch.stop();
        log.info("result = {}, elapsed = {}", p2, stopWatch.getLastTaskTimeMillis());
        assertNull(p2);
        assertTrue(stopWatch.getLastTaskTimeMillis() < 1000);
    }
}

TestResults


2017-06-23 12:01:34.434  INFO 3246 --- [           main] n.s.cachesample.service.PersonService    : called findById. id = 10
2017-06-23 12:01:44.449  INFO 3246 --- [           main] n.s.c.service.PersonServiceTest          : result = null, elapsed = 10058
2017-06-23 12:01:44.498  INFO 3246 --- [           main] n.s.c.service.PersonServiceTest          : result = null, elapsed = 49

By the way, how it is saved in Redis is as follows.

$ redis-cli -h 192.168.99.100 get 'SAMPLE:hello:10'                                                                                                                                                                                12:06:23
"{\"@class\":\"org.springframework.cache.support.NullValue\"}"

This is due to the following classes defined in GenericJackson2JsonRedisSerializer.

NullValueSerializer


/**
 * {@link StdSerializer} adding class information required by default typing. This allows de-/serialization of
 * {@link NullValue}.
 *
 * @author Christoph Strobl
 * @since 1.8
 */
private class NullValueSerializer extends StdSerializer<NullValue> {

    private static final long serialVersionUID = 1999052150548658808L;
    private final String classIdentifier;

    /**
     * @param classIdentifier can be {@literal null} and will be defaulted to {@code @class}.
     */
    NullValueSerializer(String classIdentifier) {

        super(NullValue.class);
        this.classIdentifier = StringUtils.hasText(classIdentifier) ? classIdentifier : "@class";
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
     */
    @Override
    public void serialize(NullValue value, JsonGenerator jgen, SerializerProvider provider)
            throws IOException {

        jgen.writeStartObject();
        jgen.writeStringField(classIdentifier, NullValue.class.getName());
        jgen.writeEndObject();
    }
}

https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java

Recommended Posts

Null support cache in Spring Data Redis
Exists using Specification in Spring Data JPA
To write Response data directly in Spring
About specifying the validity period when saving the cache in Redis with Spring Cache
Make the where clause variable in Spring Data JPA
SerializationException in Spring Boot (1 series) + spring-security-oauth2 + Redis Session + Heroku
Inject Logger in Spring
Spring Framework multilingual support
Use Interceptor in Spring
Spring Data JDBC Preview
Microservices in Spring Cloud
Get cookies in Spring
spring data dynamodb trap
Create API to send and receive Json data in Spring