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