Skip to content

Commit 4d038ff

Browse files
christophstroblmp911de
authored andcommitted
Cache invalid persistent property paths.
Cache failing resolution attempts to avoid reiterating on the same paths over and over again. Closes #2837 Original pull request: #2838
1 parent 8b86fb8 commit 4d038ff

File tree

2 files changed

+63
-7
lines changed

2 files changed

+63
-7
lines changed

src/main/java/org/springframework/data/mapping/context/PersistentPropertyPathFactory.java

+48-7
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@
4141
* A factory implementation to create {@link PersistentPropertyPath} instances in various ways.
4242
*
4343
* @author Oliver Gierke
44+
* @author Christoph Strobl
4445
* @since 2.1
4546
* @soundtrack Cypress Hill - Boom Biddy Bye Bye (Fugees Remix, Unreleased & Revamped)
4647
*/
4748
class PersistentPropertyPathFactory<E extends PersistentEntity<?, P>, P extends PersistentProperty<P>> {
4849

4950
private static final Predicate<PersistentProperty<? extends PersistentProperty<?>>> IS_ENTITY = PersistentProperty::isEntity;
5051

51-
private final Map<TypeAndPath, PersistentPropertyPath<P>> propertyPaths = new ConcurrentReferenceHashMap<>();
52+
private final Map<TypeAndPath, PathResolution> propertyPaths = new ConcurrentReferenceHashMap<>();
5253
private final MappingContext<E, P> context;
5354

5455
public PersistentPropertyPathFactory(MappingContext<E, P> context) {
@@ -166,7 +167,10 @@ public <T> PersistentPropertyPaths<T, P> from(TypeInformation<T> type, Predicate
166167
}
167168

168169
private PersistentPropertyPath<P> getPersistentPropertyPath(TypeInformation<?> type, String propertyPath) {
170+
return getPotentiallyCachedPath(type, propertyPath).getResolvedPath();
171+
}
169172

173+
private PathResolution getPotentiallyCachedPath(TypeInformation<?> type, String propertyPath) {
170174
return propertyPaths.computeIfAbsent(TypeAndPath.of(type, propertyPath),
171175
it -> createPersistentPropertyPath(it.getPath(), it.getType()));
172176
}
@@ -178,7 +182,7 @@ private PersistentPropertyPath<P> getPersistentPropertyPath(TypeInformation<?> t
178182
* @param type must not be {@literal null}.
179183
* @return
180184
*/
181-
private PersistentPropertyPath<P> createPersistentPropertyPath(String propertyPath, TypeInformation<?> type) {
185+
private PathResolution createPersistentPropertyPath(String propertyPath, TypeInformation<?> type) {
182186

183187
String trimmedPath = propertyPath.trim();
184188
List<String> parts = trimmedPath.isEmpty() ? Collections.emptyList() : List.of(trimmedPath.split("\\."));
@@ -196,17 +200,14 @@ private PersistentPropertyPath<P> createPersistentPropertyPath(String propertyPa
196200
Pair<DefaultPersistentPropertyPath<P>, E> pair = getPair(path, iterator, segment, current);
197201

198202
if (pair == null) {
199-
200-
String source = StringUtils.collectionToDelimitedString(parts, ".");
201-
202-
throw new InvalidPersistentPropertyPath(source, type, segment, currentPath);
203+
return new PathResolution(parts, segment, type, currentPath);
203204
}
204205

205206
path = pair.getFirst();
206207
current = pair.getSecond();
207208
}
208209

209-
return path;
210+
return new PathResolution(path);
210211
}
211212

212213
@Nullable
@@ -429,4 +430,44 @@ public int compare(PersistentPropertyPath<?> left, PersistentPropertyPath<?> rig
429430
}
430431
}
431432
}
433+
434+
/**
435+
* Wrapper around {@link PersistentPropertyPath} that allows them to be cached. Retains behaviour be throwing
436+
* {@link InvalidPersistentPropertyPath} on access of {@link #getResolvedPath()} if no corresponding property was
437+
* found.
438+
*/
439+
private static class PathResolution {
440+
441+
private final PersistentPropertyPath<?> path;
442+
private final boolean resolvable;
443+
private String source, segment;
444+
private TypeInformation<?> type;
445+
446+
public PathResolution(PersistentPropertyPath<?> path) {
447+
448+
this.path = path;
449+
this.resolvable = true;
450+
}
451+
452+
PathResolution(List<String> parts, String segment, TypeInformation<?> type, PersistentPropertyPath<?> path) {
453+
454+
this.source = StringUtils.collectionToDelimitedString(parts, ".");
455+
this.segment = segment;
456+
this.type = type;
457+
this.path = path;
458+
this.resolvable = false;
459+
}
460+
461+
/**
462+
* @return the path if available.
463+
* @throws InvalidPersistentPropertyPath when the path could not be resolved to an actual property
464+
*/
465+
PersistentPropertyPath getResolvedPath() {
466+
467+
if (resolvable) {
468+
return path;
469+
}
470+
throw new InvalidPersistentPropertyPath(source, type, segment, path);
471+
}
472+
}
432473
}

src/test/java/org/springframework/data/mapping/context/PersistentPropertyPathFactoryUnitTests.java

+15
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,25 @@
2020
import jakarta.inject.Inject;
2121

2222
import java.util.List;
23+
import java.util.Map;
2324

2425
import org.junit.jupiter.api.Test;
2526
import org.springframework.data.annotation.Reference;
2627
import org.springframework.data.mapping.MappingException;
2728
import org.springframework.data.mapping.PersistentPropertyPath;
2829
import org.springframework.data.mapping.PersistentPropertyPaths;
2930
import org.springframework.data.mapping.PropertyPath;
31+
import org.springframework.data.mapping.context.PersistentPropertyPathFactory.TypeAndPath;
3032
import org.springframework.data.mapping.model.BasicPersistentEntity;
33+
import org.springframework.data.util.TypeInformation;
34+
import org.springframework.test.util.ReflectionTestUtils;
3135
import org.springframework.util.StringUtils;
3236

3337
/**
3438
* Unit tests for {@link PersistentPropertyPathFactory}.
3539
*
3640
* @author Oliver Gierke
41+
* @author Christoph Strobl
3742
* @soundtrack Cypress Hill - Illusions (Q-Tip Remix, Unreleased & Revamped)
3843
*/
3944
class PersistentPropertyPathFactoryUnitTests {
@@ -84,6 +89,16 @@ void cachesPersistentPropertyPaths() {
8489
.isSameAs(factory.from(PersonSample.class, "persons.name"));
8590
}
8691

92+
@Test // GH-2837
93+
void cachesFailingPropertyPathLookup() {
94+
95+
assertThatExceptionOfType(InvalidPersistentPropertyPath.class)//
96+
.isThrownBy(() -> factory.from(PersonSample.class, "persons.firstname"));
97+
98+
Map<TypeAndPath, ?> propertyPaths = (Map<TypeAndPath, ?>) ReflectionTestUtils.getField(factory, "propertyPaths");
99+
assertThat(propertyPaths).containsKey(TypeAndPath.of(TypeInformation.of(PersonSample.class), "persons.firstname"));
100+
}
101+
87102
@Test // DATACMNS-1275
88103
void findsNestedPropertyByFilter() {
89104

0 commit comments

Comments
 (0)