Skip to content

Commit a9abb40

Browse files
committed
DATACMNS-332 - Performance improvements in conversion subsystem hotspots.
Added caching to DefaultTypeMapper, SimpleTypeInformationMapper, BasicPersistentEntity and PreferredConstructor as these seem to be performance hotspots on the reading side of object conversion.
1 parent a8b912c commit a9abb40

File tree

6 files changed

+139
-19
lines changed

6 files changed

+139
-19
lines changed

src/main/java/org/springframework/data/convert/DefaultTypeMapper.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.util.Arrays;
2020
import java.util.Collections;
2121
import java.util.List;
22+
import java.util.Map;
23+
import java.util.concurrent.ConcurrentHashMap;
2224

2325
import org.springframework.data.mapping.PersistentEntity;
2426
import org.springframework.data.mapping.context.MappingContext;
@@ -38,6 +40,7 @@ public class DefaultTypeMapper<S> implements TypeMapper<S> {
3840

3941
private final TypeAliasAccessor<S> accessor;
4042
private final List<? extends TypeInformationMapper> mappers;
43+
private final Map<Object, TypeInformation<?>> typeCache;
4144

4245
/**
4346
* Creates a new {@link DefaultTypeMapper} using the given {@link TypeAliasAccessor}. It will use a
@@ -84,6 +87,7 @@ public DefaultTypeMapper(TypeAliasAccessor<S> accessor,
8487

8588
this.mappers = Collections.unmodifiableList(mappers);
8689
this.accessor = accessor;
90+
this.typeCache = new ConcurrentHashMap<Object, TypeInformation<?>>();
8791
}
8892

8993
/*
@@ -93,21 +97,37 @@ public DefaultTypeMapper(TypeAliasAccessor<S> accessor,
9397
public TypeInformation<?> readType(S source) {
9498

9599
Assert.notNull(source);
100+
96101
Object alias = accessor.readAliasFrom(source);
102+
return alias == null ? null : getFromCacheOrCreate(alias);
103+
}
104+
105+
/**
106+
* Tries to lookup a {@link TypeInformation} for the given alias from the cache and return it if found. If none is
107+
* found it'll consult the {@link TypeInformationMapper}s and cache the value found.
108+
*
109+
* @param alias
110+
* @return
111+
*/
112+
private TypeInformation<?> getFromCacheOrCreate(Object alias) {
113+
114+
TypeInformation<?> typeInformation = typeCache.get(alias);
97115

98-
if (alias == null) {
99-
return null;
116+
if (typeInformation != null) {
117+
return typeInformation;
100118
}
101119

102120
for (TypeInformationMapper mapper : mappers) {
103-
TypeInformation<?> type = mapper.resolveTypeFrom(alias);
104121

105-
if (type != null) {
106-
return type;
122+
typeInformation = mapper.resolveTypeFrom(alias);
123+
124+
if (typeInformation != null) {
125+
typeCache.put(alias, typeInformation);
126+
return typeInformation;
107127
}
108128
}
109129

110-
return null;
130+
return typeInformation;
111131
}
112132

113133
/*

src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.data.convert;
1717

18+
import java.util.Map;
19+
import java.util.concurrent.ConcurrentHashMap;
20+
1821
import org.springframework.data.util.ClassTypeInformation;
1922
import org.springframework.data.util.TypeInformation;
2023
import org.springframework.util.ClassUtils;
@@ -30,6 +33,7 @@
3033
public class SimpleTypeInformationMapper implements TypeInformationMapper {
3134

3235
public static final SimpleTypeInformationMapper INSTANCE = new SimpleTypeInformationMapper();
36+
private static final Map<String, TypeInformation<?>> cache = new ConcurrentHashMap<String, TypeInformation<?>>();
3337

3438
/**
3539
* Returns the {@link TypeInformation} that shall be used when the given {@link String} value is found as type hint.
@@ -52,11 +56,23 @@ public TypeInformation<?> resolveTypeFrom(Object alias) {
5256
return null;
5357
}
5458

59+
TypeInformation<?> information = cache.get(value);
60+
61+
if (information != null) {
62+
return information;
63+
}
64+
5565
try {
56-
return ClassTypeInformation.from(ClassUtils.forName(value, null));
66+
information = ClassTypeInformation.from(ClassUtils.forName(value, null));
5767
} catch (ClassNotFoundException e) {
5868
return null;
5969
}
70+
71+
if (information != null) {
72+
cache.put(value, information);
73+
}
74+
75+
return information;
6076
}
6177

6278
/**
@@ -67,7 +83,6 @@ public TypeInformation<?> resolveTypeFrom(Object alias) {
6783
* @return the String representation to be stored or {@literal null} if no type information shall be stored.
6884
*/
6985
public String createAliasFor(TypeInformation<?> type) {
70-
7186
return type.getType().getName();
7287
}
7388
}

src/main/java/org/springframework/data/mapping/PreferredConstructor.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2011-2013 by the original author(s).
2+
* Copyright 2011-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -21,6 +21,8 @@
2121
import java.lang.reflect.Constructor;
2222
import java.util.Arrays;
2323
import java.util.List;
24+
import java.util.Map;
25+
import java.util.concurrent.ConcurrentHashMap;
2426

2527
import org.springframework.beans.factory.annotation.Value;
2628
import org.springframework.data.annotation.PersistenceConstructor;
@@ -39,6 +41,7 @@ public class PreferredConstructor<T, P extends PersistentProperty<P>> {
3941

4042
private final Constructor<T> constructor;
4143
private final List<Parameter<Object, P>> parameters;
44+
private final Map<PersistentProperty<?>, Boolean> isPropertyParameterCache = new ConcurrentHashMap<PersistentProperty<?>, Boolean>();
4245

4346
/**
4447
* Creates a new {@link PreferredConstructor} from the given {@link Constructor} and {@link Parameter}s.
@@ -114,12 +117,20 @@ public boolean isConstructorParameter(PersistentProperty<?> property) {
114117

115118
Assert.notNull(property);
116119

120+
Boolean cached = isPropertyParameterCache.get(property);
121+
122+
if (cached != null) {
123+
return cached;
124+
}
125+
117126
for (Parameter<?, P> parameter : parameters) {
118127
if (parameter.maps(property)) {
128+
isPropertyParameterCache.put(property, true);
119129
return true;
120130
}
121131
}
122132

133+
isPropertyParameterCache.put(property, false);
123134
return false;
124135
}
125136

src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
import java.io.Serializable;
1919
import java.util.Comparator;
20+
import java.util.HashMap;
2021
import java.util.HashSet;
22+
import java.util.Map;
2123
import java.util.Set;
2224
import java.util.TreeSet;
2325

@@ -46,6 +48,8 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
4648
private final Set<P> properties;
4749
private final Set<Association<P>> associations;
4850

51+
private final Map<String, P> propertyCache = new HashMap<String, P>();
52+
4953
private P idProperty;
5054
private P versionProperty;
5155

@@ -157,6 +161,7 @@ public void addPersistentProperty(P property) {
157161

158162
Assert.notNull(property);
159163
properties.add(property);
164+
propertyCache.put(property.getName(), property);
160165

161166
if (property.isIdProperty()) {
162167

@@ -193,14 +198,7 @@ public void addAssociation(Association<P> association) {
193198
* @see org.springframework.data.mapping.PersistentEntity#getPersistentProperty(java.lang.String)
194199
*/
195200
public P getPersistentProperty(String name) {
196-
197-
for (P property : properties) {
198-
if (property.getName().equals(name)) {
199-
return property;
200-
}
201-
}
202-
203-
return null;
201+
return propertyCache.get(name);
204202
}
205203

206204
/*
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.convert;
17+
18+
import static org.hamcrest.CoreMatchers.*;
19+
import static org.junit.Assert.*;
20+
import static org.mockito.Mockito.*;
21+
22+
import java.util.Arrays;
23+
import java.util.Collections;
24+
import java.util.Map;
25+
26+
import org.junit.Before;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.mockito.Mock;
30+
import org.mockito.runners.MockitoJUnitRunner;
31+
import org.springframework.data.util.ClassTypeInformation;
32+
import org.springframework.data.util.TypeInformation;
33+
34+
/**
35+
* Unit tests for {@link DefaultTypeMapper}.
36+
*
37+
* @author Oliver Gierke
38+
*/
39+
@RunWith(MockitoJUnitRunner.class)
40+
public class DefaultTypeMapperUnitTests {
41+
42+
static final String STRING = String.class.getName();
43+
44+
@Mock
45+
TypeAliasAccessor<Map<String, String>> accessor;
46+
47+
@Mock
48+
TypeInformationMapper mapper;
49+
50+
TypeMapper<Map<String, String>> typeMapper;
51+
Map<String, String> source;
52+
53+
@Before
54+
@SuppressWarnings({ "rawtypes", "unchecked" })
55+
public void setUp() {
56+
57+
this.typeMapper = new DefaultTypeMapper<Map<String, String>>(accessor, Arrays.asList(mapper));
58+
this.source = Collections.singletonMap("key", STRING);
59+
60+
when(accessor.readAliasFrom(source)).thenReturn(STRING);
61+
when(mapper.resolveTypeFrom(STRING)).thenReturn((TypeInformation) ClassTypeInformation.from(String.class));
62+
}
63+
64+
@Test
65+
@SuppressWarnings("rawtypes")
66+
public void cachesResolvedTypeInformation() {
67+
68+
TypeInformation<?> information = typeMapper.readType(source);
69+
assertThat(information, is((TypeInformation) ClassTypeInformation.from(String.class)));
70+
verify(mapper, times(1)).resolveTypeFrom(STRING);
71+
72+
typeMapper.readType(source);
73+
verify(mapper, times(1)).resolveTypeFrom(STRING);
74+
}
75+
}

src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2012-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import org.springframework.data.util.TypeInformation;
2424

2525
/**
26+
* Unit tests for {@link SimpleTypeInformationMapper}.
2627
*
2728
* @author Oliver Gierke
2829
*/

0 commit comments

Comments
 (0)