Application Scenarios
We want to reduce the database pressure by reducing the number of queries to the relational database through caching. In the execution of the DAO classselect***(), thequery***()method, the firstRedisquery from the cache data, if there is a direct from theRedisresults, if there is no further to the database to initiate the query request fetching data.
Serialization issues
To save domain object as Key-value on a redis, you have to solve the object serialization problem. Spring Data Redis provides us with some ready-made solutions:
- JdkSerializationRedisSerializer. Use the serialization functionality provided by the JDK. The advantage is that you do not need to provide type information when deserializing (class), but the drawback is that the result of serialization is very large, about 5 times times the JSON format, which consumes a lot of memory from the Redis server.
- Jackson2JsonRedisSerializer.Jacksonserializes an object into a JSON string using the library. The advantage is that fast, serialized strings are short and concise. But the disadvantage is also very deadly, that is, the constructor of this class has a type parameter and must provide the type information (object) of the object to serialize.class. By looking at the source code, it is discovered that only type information is used during deserialization.
If you use scenario one, you have to pay the cache more than 4 times times the cost of memory, it can not afford. If you use scenario two, you must configure a serializer for each domain object, that is, if there are 100 domain objects in my app, you must configure 100 in the spring configuration fileJackson2JsonRedisSerializer, which is obviously unrealistic.
With Google, a #145 pull request was found in the Spring data Redis project, and the content of the submission request was the answerJacksonto the question of having to provide type information. Unfortunately, however, this request has not yet been takenmerge. But we can offload the code copy to our project:
/**
* @author Christoph Strobl
* @since 1.6
*/
public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Object> {
private final ObjectMapper mapper;
/**
* Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
*/
public GenericJackson2JsonRedisSerializer() {
this((String) null);
}
/**
* Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing using the
* given {@literal name}. In case of an {@literal empty} or {@literal null} String the default
* {@link JsonTypeInfo.Id#CLASS} will be used.
*
* @param classPropertyTypeName Name of the JSON property holding type information. Can be {@literal null}.
*/ public GenericJackson2JsonRedisSerializer(String classPropertyTypeName) {
this(new ObjectMapper());
if (StringUtils.hasText(classPropertyTypeName)) {
mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, classPropertyTypeName);
} else {
mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
}
}
/**
* Setting a custom-configured {@link ObjectMapper} is one way to take further control of the JSON serialization
* process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
* specific types.
*
* @param mapper must not be {@literal null}.
*/
public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {
Assert.notNull(mapper, "ObjectMapper must not be null!");
this.mapper = mapper;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.serializer.RedisSerializer#serialize(java.lang.Object)
*/
@Override
public byte[] serialize(Object source) throws SerializationException {
if (source == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return mapper.writeValueAsBytes(source);
} catch (JsonProcessingException e) {
throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.serializer.RedisSerializer#deserialize(byte[])
*/
@Override
public Object deserialize(byte[] source) throws SerializationException {
return deserialize(source, Object.class);
}
/**
* @param source can be {@literal null}.
* @param type must not be {@literal null}.
* @return {@literal null} for empty source.
* @throws SerializationException */
public <T> T deserialize(byte[] source, Class<T> type) throws SerializationException {
Assert.notNull(type,
"Deserialization type must not be null! Pleaes provide Object.class to make use of Jackson2 default typing.");
if (SerializationUtils.isEmpty(source)) {
return null;
}
try {
return mapper.readValue(source, type);
} catch (Exception ex) {
throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
}
}
}
Then use this in the configuration fileGenericJackson2JsonRedisSerializer:
<bean id="jacksonSerializer" class="com.fh.taolijie.component.GenericJackson2JsonRedisSerializer"> </bean>
Rebuilding the deployment, we found that this serializer can support many different types of domain objects at the same time, problem solving.
Solve serialization problems in query results using Spring cache + Redis + Jackson Serializer Cache database