DATAREDIS-543 - Allow customized type aliasing.
We now support customization of type aliasing for types using through Redis Repositories. Type aliasing can be customized by either annotating types with @TypeAlias or by providing a RedisTypeMapper to MappingRedisConverter.
Map<Class<?>, String> mapping = new HashMap<>();
mapping.put(Person.class, "person");
ConfigurableTypeInformationMapper mapper = new ConfigurableTypeInformationMapper(mapping);
RedisTypeMapper typeMapper = new DefaultRedisTypeMapper(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, Collections.singletonList(mapper))
alternatively:
@RedisHash
@TypeAlias("person")
class Person {
// …
}
Original Pull Request: #299
This commit is contained in:
committed by
Christoph Strobl
parent
f3128bf0c0
commit
04153e07b0
@@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Reference;
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
import org.springframework.data.redis.core.TimeToLive;
|
||||
import org.springframework.data.redis.core.index.Indexed;
|
||||
@@ -109,6 +110,7 @@ public class ConversionTestEntities {
|
||||
}
|
||||
}
|
||||
|
||||
@TypeAlias("with-post-code")
|
||||
public static class AddressWithPostcode extends Address {
|
||||
|
||||
String postcode;
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2017 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.core.convert;
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.ConfigurableTypeInformationMapper;
|
||||
import org.springframework.data.convert.SimpleTypeInformationMapper;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultRedisTypeMapper}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class DefaultRedisTypeMapperUnitTests {
|
||||
|
||||
GenericConversionService conversionService;
|
||||
ConfigurableTypeInformationMapper configurableTypeInformationMapper;
|
||||
SimpleTypeInformationMapper simpleTypeInformationMapper;
|
||||
DefaultRedisTypeMapper typeMapper;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
conversionService = new GenericConversionService();
|
||||
new RedisCustomConversions().registerConvertersIn(conversionService);
|
||||
|
||||
configurableTypeInformationMapper = new ConfigurableTypeInformationMapper(singletonMap(String.class, "1"));
|
||||
simpleTypeInformationMapper = new SimpleTypeInformationMapper();
|
||||
|
||||
typeMapper = new DefaultRedisTypeMapper(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void defaultInstanceWritesClasses() {
|
||||
writesTypeToField(new Bucket(), String.class, String.class.getName());
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void defaultInstanceReadsClasses() {
|
||||
|
||||
Bucket bucket = Bucket
|
||||
.newBucketFromStringMap(singletonMap(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, String.class.getName()));
|
||||
readsTypeFromField(bucket, String.class);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void writesMapKeyForType() {
|
||||
|
||||
typeMapper = new DefaultRedisTypeMapper(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY,
|
||||
Collections.singletonList(configurableTypeInformationMapper));
|
||||
|
||||
writesTypeToField(new Bucket(), String.class, "1");
|
||||
writesTypeToField(new Bucket(), Object.class, null);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void writesClassNamesForUnmappedValuesIfConfigured() {
|
||||
|
||||
typeMapper = new DefaultRedisTypeMapper(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY,
|
||||
Arrays.asList(configurableTypeInformationMapper, simpleTypeInformationMapper));
|
||||
|
||||
writesTypeToField(new Bucket(), String.class, "1");
|
||||
writesTypeToField(new Bucket(), Object.class, Object.class.getName());
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void readsTypeForMapKey() {
|
||||
|
||||
typeMapper = new DefaultRedisTypeMapper(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY,
|
||||
Collections.singletonList(configurableTypeInformationMapper));
|
||||
|
||||
readsTypeFromField(Bucket.newBucketFromStringMap(singletonMap(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, "1")),
|
||||
String.class);
|
||||
readsTypeFromField(Bucket.newBucketFromStringMap(singletonMap(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, "unmapped")),
|
||||
null);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void readsTypeLoadingClassesForUnmappedTypesIfConfigured() {
|
||||
|
||||
typeMapper = new DefaultRedisTypeMapper(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY,
|
||||
Arrays.asList(configurableTypeInformationMapper, simpleTypeInformationMapper));
|
||||
|
||||
readsTypeFromField(new Bucket(singletonMap(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, "1".getBytes())), String.class);
|
||||
readsTypeFromField(
|
||||
new Bucket(singletonMap(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, Object.class.getName().getBytes())),
|
||||
Object.class);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void addsFullyQualifiedClassNameUnderDefaultKeyByDefault() {
|
||||
writesTypeToField(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, new Bucket(), String.class);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void writesTypeToCustomFieldIfConfigured() {
|
||||
typeMapper = new DefaultRedisTypeMapper("_custom");
|
||||
writesTypeToField("_custom", new Bucket(), String.class);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void doesNotWriteTypeInformationInCaseKeyIsSetToNull() {
|
||||
typeMapper = new DefaultRedisTypeMapper(null);
|
||||
writesTypeToField(null, new Bucket(), String.class);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void readsTypeFromDefaultKeyByDefault() {
|
||||
readsTypeFromField(
|
||||
Bucket.newBucketFromStringMap(singletonMap(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, String.class.getName())),
|
||||
String.class);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void readsTypeFromCustomFieldConfigured() {
|
||||
|
||||
typeMapper = new DefaultRedisTypeMapper("_custom");
|
||||
readsTypeFromField(Bucket.newBucketFromStringMap(singletonMap("_custom", String.class.getName())), String.class);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void returnsListForBasicDBLists() {
|
||||
readsTypeFromField(new Bucket(), null);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void returnsNullIfNoTypeInfoInBucket() {
|
||||
|
||||
readsTypeFromField(new Bucket(), null);
|
||||
readsTypeFromField(Bucket.newBucketFromStringMap(singletonMap(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, "")), null);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void returnsNullIfClassCannotBeLoaded() {
|
||||
|
||||
readsTypeFromField(Bucket.newBucketFromStringMap(singletonMap(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, "fooBar")),
|
||||
null);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void returnsNullIfTypeKeySetToNull() {
|
||||
|
||||
typeMapper = new DefaultRedisTypeMapper(null);
|
||||
readsTypeFromField(
|
||||
Bucket.newBucketFromStringMap(singletonMap(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, String.class.getName())),
|
||||
null);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void returnsCorrectTypeKey() {
|
||||
|
||||
assertThat(typeMapper.isTypeKey(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY)).isTrue();
|
||||
|
||||
typeMapper = new DefaultRedisTypeMapper("_custom");
|
||||
assertThat(typeMapper.isTypeKey("_custom")).isTrue();
|
||||
assertThat(typeMapper.isTypeKey(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY)).isFalse();
|
||||
|
||||
typeMapper = new DefaultRedisTypeMapper(null);
|
||||
assertThat(typeMapper.isTypeKey("_custom")).isFalse();
|
||||
assertThat(typeMapper.isTypeKey(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY)).isFalse();
|
||||
}
|
||||
|
||||
private void readsTypeFromField(Bucket bucket, @Nullable Class<?> type) {
|
||||
|
||||
TypeInformation<?> typeInfo = typeMapper.readType(BucketPropertyPath.from(bucket));
|
||||
|
||||
if (type != null) {
|
||||
assertThat(typeInfo).isNotNull();
|
||||
assertThat(typeInfo.getType()).isAssignableFrom(type);
|
||||
} else {
|
||||
assertThat(typeInfo).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
private void writesTypeToField(@Nullable String field, Bucket bucket, Class<?> type) {
|
||||
|
||||
typeMapper.writeType(type, BucketPropertyPath.from(bucket));
|
||||
|
||||
if (field == null) {
|
||||
assertThat(bucket.keySet()).isEmpty();
|
||||
} else {
|
||||
assertThat(bucket.asMap()).containsKey(field);
|
||||
assertThat(bucket.get(field)).isEqualTo(type.getName().getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
private void writesTypeToField(Bucket bucket, Class<?> type, @Nullable Object value) {
|
||||
|
||||
typeMapper.writeType(type, BucketPropertyPath.from(bucket));
|
||||
|
||||
if (value == null) {
|
||||
assertThat(bucket.keySet()).isEmpty();
|
||||
} else {
|
||||
|
||||
byte[] expected = value instanceof Class ? ((Class) value).getName().getBytes() : value.toString().getBytes();
|
||||
|
||||
assertThat(bucket.asMap()).containsKey(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY);
|
||||
assertThat(bucket.get(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY)).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,16 +32,7 @@ import java.time.LocalTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
import org.hamcrest.core.IsEqual;
|
||||
import org.junit.Before;
|
||||
@@ -94,6 +85,15 @@ public class MappingRedisConverterUnitTests {
|
||||
assertThat(write(rand).getBucket(), isBucket().containingTypeHint("_class", Person.class));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void writeSkipsTypeHintIfConfigured() {
|
||||
|
||||
converter = new MappingRedisConverter(new RedisMappingContext(), null, resolverMock);
|
||||
converter.afterPropertiesSet();
|
||||
|
||||
assertThat(write(rand).getBucket(), isBucket().containingTypeHint("_class", Person.class));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-425
|
||||
public void writeAppendsKeyCorrectly() {
|
||||
|
||||
@@ -205,7 +205,7 @@ public class MappingRedisConverterUnitTests {
|
||||
assertThat(target.getBucket(), isBucket().without("address._class"));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-425
|
||||
@Test // DATAREDIS-425, DATAREDIS-543
|
||||
public void writeAddsClassTypeInformationCorrectlyForNonMatchingTypes() {
|
||||
|
||||
AddressWithPostcode address = new AddressWithPostcode();
|
||||
@@ -216,7 +216,7 @@ public class MappingRedisConverterUnitTests {
|
||||
|
||||
RedisData target = write(rand);
|
||||
|
||||
assertThat(target.getBucket(), isBucket().containingTypeHint("address._class", AddressWithPostcode.class));
|
||||
assertThat(target.getBucket(), isBucket().containingUtf8String("address._class", "with-post-code"));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-425
|
||||
|
||||
@@ -15,19 +15,38 @@
|
||||
*/
|
||||
package org.springframework.data.redis.repository;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.data.convert.ConfigurableTypeInformationMapper;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.convert.DefaultRedisTypeMapper;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter;
|
||||
import org.springframework.data.redis.core.convert.RedisCustomConversions;
|
||||
import org.springframework.data.redis.core.convert.RedisTypeMapper;
|
||||
import org.springframework.data.redis.core.convert.ReferenceResolver;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@@ -45,10 +64,52 @@ public class RedisRepositoryIntegrationTests extends RedisRepositoryIntegrationT
|
||||
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
|
||||
RedisTemplate<String, String> template = new RedisTemplate<>();
|
||||
template.setDefaultSerializer(StringRedisSerializer.UTF_8);
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MappingRedisConverter redisConverter(RedisMappingContext mappingContext,
|
||||
RedisCustomConversions customConversions, ReferenceResolver referenceResolver) {
|
||||
|
||||
MappingRedisConverter mappingRedisConverter = new MappingRedisConverter(mappingContext, null, referenceResolver,
|
||||
customTypeMapper());
|
||||
|
||||
mappingRedisConverter.setCustomConversions(customConversions);
|
||||
|
||||
return mappingRedisConverter;
|
||||
}
|
||||
|
||||
private RedisTypeMapper customTypeMapper() {
|
||||
|
||||
Map<Class<?>, String> mapping = new HashMap<>();
|
||||
|
||||
mapping.put(Person.class, "person");
|
||||
mapping.put(City.class, "city");
|
||||
|
||||
ConfigurableTypeInformationMapper mapper = new ConfigurableTypeInformationMapper(mapping);
|
||||
|
||||
return new DefaultRedisTypeMapper(DefaultRedisTypeMapper.DEFAULT_TYPE_KEY, Collections.singletonList(mapper));
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired RedisOperations<String, String> operations;
|
||||
|
||||
@Test // DATAREDIS-543
|
||||
public void shouldConsiderCustomTypeMapper() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.id = "rand";
|
||||
rand.firstname = "rand";
|
||||
rand.lastname = "al'thor";
|
||||
|
||||
repo.save(rand);
|
||||
|
||||
Map<String, String> entries = operations.<String, String> opsForHash().entries("persons:rand");
|
||||
|
||||
assertThat(entries.get("_class"), is(equalTo("person")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user