DATAREDIS-503 - Polishing.

Rename ConvertingHashMapper to ObjectHashMapper. Add fromHash with type to avoid casting. Add JavaDoc documentation to HashMapper. Add hash mapping to reference documentation.

Original pull request: #194.
This commit is contained in:
Mark Paluch
2016-04-29 16:42:08 +02:00
parent 359c3e4533
commit c1476b701b
5 changed files with 235 additions and 123 deletions

View File

@@ -50,13 +50,13 @@ http://github.com/xetorthio/jedis[Jedis] is one of the connectors supported by t
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Jedis ConnectionFactory -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"/>
</beans>
----
@@ -64,14 +64,14 @@ For production use however, one might want to tweak the settings such as the hos
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="server" p:port="6379" />
</beans>
----
@@ -85,13 +85,13 @@ A typical JRedis configuration can looks like this:
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jredisConnectionFactory" class="org.springframework.data.redis.connection.jredis.JredisConnectionFactory" p:host-name="server" p:port="6379"/>
</beans>
----
@@ -101,9 +101,9 @@ The configuration is quite similar to Jedis, with one notable exception. By defa
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jredisConnectionFactory" class="org.springframework.data.redis.connection.jredis.JredisConnectionFactory">
<constructor-arg>
<bean class="org.springframework.data.redis.connection.jredis.DefaultJredisPool">
@@ -112,7 +112,7 @@ The configuration is quite similar to Jedis, with one notable exception. By defa
</bean>
</constructor-arg>
</bean>
</beans>
----
@@ -125,14 +125,14 @@ By now, its configuration is probably easy to guess:
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="srpConnectionFactory" class="org.springframework.data.redis.connection.srp.SrpConnectionFactory" p:host-name="server" p:port="6379"/>
</beans>
----
@@ -148,13 +148,13 @@ Its configuration is probably easy to guess:
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="lettuceConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory" p:host-name="server" p:port="6379"/>
</beans>
----
@@ -172,12 +172,12 @@ NOTE: Please note that currently only http://github.com/xetorthio/jedis[Jedis] a
/**
* jedis
*/
@Bean
@Bean
public RedisConnectionFactory jedisConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster")
.sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380);
return new JedisConnectionFactory(sentinelConfig);
}
}
/**
* lettuce
@@ -262,33 +262,33 @@ For cases where a certain template *view* is needed, declare the view as a depen
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
...
...
</beans>
----
[source,java]
----
public class Example {
// inject the actual template
@Autowired
private RedisTemplate<String, String> template;
public class Example {
// inject the actual template
@Autowired
private RedisTemplate<String, String> template;
// inject the template as ListOperations
@Resource(name="redisTemplate")
@Resource(name="redisTemplate")
private ListOperations<String, String> listOps;
public void addLink(String userId, URL url) {
listOps.leftPush(userId, url.toExternalForm());
listOps.leftPush(userId, url.toExternalForm());
}
}
----
@@ -301,27 +301,27 @@ Since it's quite common for the keys and values stored in Redis to be `java.lang
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
...
...
</beans>
----
[source,java]
----
public class Example {
public class Example {
@Autowired
private StringRedisTemplate redisTemplate;
private StringRedisTemplate redisTemplate;
public void addLink(String userId, URL url) {
redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
}
}
----
@@ -330,12 +330,12 @@ As with the other Spring templates, `RedisTemplate` and `StringRedisTemplate` al
[source,java]
----
public void useCallback() {
public void useCallback() {
redisTemplate.execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
redisTemplate.execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
Long size = connection.dbSize();
// Can cast to StringRedisConnection if using a StringRedisTemplate
// Can cast to StringRedisConnection if using a StringRedisTemplate
((StringRedisConnection)connection).set("key", "value");
}
});
@@ -345,7 +345,57 @@ public void useCallback() {
[[redis:serializer]]
== Serializers
From the framework perspective, the data stored in Redis is just bytes. While Redis itself supports various types, for the most part these refer to the way the data is stored rather than what it represents. It is up to the user to decide whether the information gets translated into Strings or any other objects. The conversion between the user (custom) types and raw data (and vice-versa) is handled in Spring Data Redis through the `RedisSerializer` interface (package `org.springframework.data.redis.serializer`) which as the name implies, takes care of the serialization process. Multiple implementations are available out of the box, two of which have been already mentioned before in this documentation: the `StringRedisSerializer` and the `JdkSerializationRedisSerializer`. However one can use `OxmSerializer` for Object/XML mapping through Spring 3 http://docs.spring.io/spring/docs/current/spring-framework-reference/html/oxm.html[OXM] support or either `JacksonJsonRedisSerializer`, `Jackson2JsonRedisSerializer` or `GenericJackson2JsonRedisSerializer for storing data in http://en.wikipedia.org/wiki/JSON[JSON] format. Do note that the storage format is not limited only to values - it can be used for keys, values or hashes without any restrictions.
From the framework perspective, the data stored in Redis is just bytes. While Redis itself supports various types, for the most part these refer to the way the data is stored rather than what it represents. It is up to the user to decide whether the information gets translated into Strings or any other objects. The conversion between the user (custom) types and raw data (and vice-versa) is handled in Spring Data Redis through the `RedisSerializer` interface (package `org.springframework.data.redis.serializer`) which as the name implies, takes care of the serialization process. Multiple implementations are available out of the box, two of which have been already mentioned before in this documentation: the `StringRedisSerializer` and the `JdkSerializationRedisSerializer`. However one can use `OxmSerializer` for Object/XML mapping through Spring 3 http://docs.spring.io/spring/docs/current/spring-framework-reference/html/oxm.html[OXM] support or either `JacksonJsonRedisSerializer`, `Jackson2JsonRedisSerializer` or `GenericJackson2JsonRedisSerializer` for storing data in http://en.wikipedia.org/wiki/JSON[JSON] format. Do note that the storage format is not limited only to values - it can be used for keys, values or hashes without any restrictions.[[redis:serializer]]
== Hash mapping
Data can be stored using various data structures within Redis. You already learned about `Jackson2JsonRedisSerializer` which can convert objects
in http://en.wikipedia.org/wiki/JSON[JSON] format. JSON can be ideally stored as value using plain keys. A more sophisticated mapping of structured objects
can be achieved using Redis Hashes. Spring Data Redis offers various strategies for mapping data to hashes depending on the use case.
1. Direct mapping using `HashOperations` and a <<redis:serializer,serializer>>
2. Using <<redis.repositories>>
3. Using `HashMapper` and `HashOperations`
=== Hash mappers
Hash mappers are converters to map objects to a `Map<K, V>` and back. `HashMapper` is intended for using with Redis Hashes.
Multiple implementations are available out of the box:
1. `BeanUtilsHashMapper` using Spring's http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html[BeanUtils]
2. `ObjectHashMapper` using <<redis.repositories.mapping>>
[source,java]
----
public class Person {
String firstname;
String lastname;
// …
}
public class HashMapping {
@Autowired
HashOperations<String, byte[], byte[]> hashOperations;
HashMapper<Object, byte[], byte[]> mapper = new ObjectHashMapper();
public void writeHash(String key, Person person) {
Map<byte[], byte[]> mappedHash = mapper.toHash(person);
hashOperations.putAll(key, mappedHash);
}
public Person loadHash(String key) {
Map<byte[], byte[]> loadedHash = hashOperations.entries("key");
return (Person) mapper.fromHash(loadedHash);
}
}
----
:leveloffset: 2
include::{referenceDir}/redis-messaging.adoc[]
@@ -371,27 +421,27 @@ _FIFO (First-In-First-Out)_, _LIFO (Last-In-First-Out)_ or _capped collection_ w
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="queue" class="org.springframework.data.redis.support.collections.DefaultRedisList">
<constructor-arg ref="redisTemplate"/>
<constructor-arg value="queue-key"/>
<bean id="queue" class="org.springframework.data.redis.support.collections.DefaultRedisList">
<constructor-arg ref="redisTemplate"/>
<constructor-arg value="queue-key"/>
</bean>
</beans>
----
[source,java]
----
public class AnotherExample {
public class AnotherExample {
// injected
private Deque<String> queue;
public void addTag(String tag) {
queue.push(tag);
}
@@ -408,18 +458,18 @@ Spring Redis provides an implementation for Spring http://docs.spring.io/spring/
[source,xml]
----
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- turn on declarative caching -->
<cache:annotation-driven />
<!-- turn on declarative caching -->
<cache:annotation-driven />
<!-- declare Redis Cache Manager -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager" c:template-ref="redisTemplate"/>
</beans>
<!-- declare Redis Cache Manager -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager" c:template-ref="redisTemplate"/>
</beans>
----
NOTE: By default `RedisCacheManager` will lazily initialize `RedisCache` whenever a `Cache` is requested. This can be changed by predefining a `Set` of cache names.

View File

@@ -1,12 +1,12 @@
/*
* Copyright 2011-2013 the original author or authors.
*
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,12 +20,28 @@ import java.util.Map;
/**
* Core mapping contract between Java types and Redis hashes/maps. It's up to the implementation to support nested
* objects.
*
*
* @param <T> Object type
* @param <K> Redis Hash field type
* @param <V> Redis Hash value type
* @author Costin Leau
* @author Mark Paluch
*/
public interface HashMapper<T, K, V> {
/**
* Convert an {@code object} to a map that can be used with Redis hashes.
*
* @param object
* @return
*/
Map<K, V> toHash(T object);
/**
* Convert a {@code hash} (map) to an object.
*
* @param hash
* @return
*/
T fromHash(Map<K, V> hash);
}

View File

@@ -30,7 +30,7 @@ import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.util.TypeInformation;
/**
* {@link HashMapper} based on {@link MappingRedisConverter}. Does supports nested properties and simple types like
* {@link HashMapper} based on {@link MappingRedisConverter}. Supports nested properties and simple types like
* {@link String}.
*
* <pre>
@@ -38,6 +38,7 @@ import org.springframework.data.util.TypeInformation;
* class Person {
*
* String firstname;
* String lastname;
*
* List&lt;String&gt; nicknames;
* List&lt;Person&gt; coworkers;
@@ -62,25 +63,26 @@ import org.springframework.data.util.TypeInformation;
* </pre>
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.8
*/
public class ConvertingHashMapper implements HashMapper<Object, byte[], byte[]> {
public class ObjectHashMapper implements HashMapper<Object, byte[], byte[]> {
private final MappingRedisConverter converter;
/**
* Creates new {@link ConvertingHashMapper}.
* Creates new {@link ObjectHashMapper}.
*/
public ConvertingHashMapper() {
public ObjectHashMapper() {
this(new CustomConversions());
}
/**
* Creates new {@link ConvertingHashMapper}.
* Creates new {@link ObjectHashMapper}.
*
* @param customConversions can be {@literal null}.
*/
public ConvertingHashMapper(CustomConversions customConversions) {
public ObjectHashMapper(CustomConversions customConversions) {
MappingRedisConverter mappingConverter = new MappingRedisConverter(new RedisMappingContext(),
new NoOpIndexResolver(), new NoOpReferenceResolver());
@@ -120,6 +122,18 @@ public class ConvertingHashMapper implements HashMapper<Object, byte[], byte[]>
return converter.read(Object.class, new RedisData(hash));
}
/**
* Convert a {@code hash} (map) to an object and return the casted result.
*
* @param hash
* @param type
* @param <T>
* @return
*/
public <T> T fromHash(Map<byte[], byte[]> hash, Class<T> type) {
return (T) fromHash(hash);
}
/**
* {@link ReferenceResolver} implementation always returning an empty {@link Map}.
*

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.mapping;
import org.junit.Test;
import org.springframework.data.redis.hash.ConvertingHashMapper;
/**
* @author Christoph Strobl
*/
public class ConvertingHashMapperTests extends AbstractHashMapperTest {
protected ConvertingHashMapper mapperFor(Class t) {
return new ConvertingHashMapper();
}
/**
* @see DATAREDIS-503
*/
@Test
public void testSimpleType() {
assertBackAndForwardMapping(new Integer(100));
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.mapping;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import java.util.Map;
import org.junit.Test;
import org.springframework.data.redis.hash.ObjectHashMapper;
/**
* @author Christoph Strobl
* @author Mark Paluch
*/
public class ObjectHashMapperTests extends AbstractHashMapperTest {
protected ObjectHashMapper mapperFor(Class t) {
return new ObjectHashMapper();
}
/**
* @see DATAREDIS-503
*/
@Test
public void testSimpleType() {
assertBackAndForwardMapping(new Integer(100));
}
/**
* @see DATAREDIS-503
*/
@Test
public void fromHashShouldCastToType() {
ObjectHashMapper objectHashMapper = new ObjectHashMapper();
Map<byte[], byte[]> hash = objectHashMapper.toHash(new Integer(100));
Integer result = objectHashMapper.fromHash(hash, Integer.class);
assertThat(result, is(equalTo(new Integer(100))));
}
/**
* @see DATAREDIS-503
*/
@Test(expected = ClassCastException.class)
public void fromHashShouldFailIfTypeDoesNotMatch() {
ObjectHashMapper objectHashMapper = new ObjectHashMapper();
Map<byte[], byte[]> hash = objectHashMapper.toHash(new Integer(100));
String result = objectHashMapper.fromHash(hash, String.class);
}
}