Skip to content

Commit b6abdb8

Browse files
committed
GH-2487 - Improve performance of NodeDescription determination.
1 parent e37097a commit b6abdb8

File tree

3 files changed

+74
-23
lines changed

3 files changed

+74
-23
lines changed

src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jEntityConverter.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
7878
private final Type relationshipType;
7979
private final Type mapType;
8080
private final Type listType;
81+
private final Map<String, Collection<Node>> labelNodeCache = new HashMap<>();
8182

8283
DefaultNeo4jEntityConverter(EntityInstantiators entityInstantiators, Neo4jConversionService conversionService,
8384
NodeDescriptionStore nodeDescriptionStore, TypeSystem typeSystem) {
@@ -101,6 +102,8 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
101102
public <R> R read(Class<R> targetType, MapAccessor mapAccessor) {
102103

103104
knownObjects.nextRecord();
105+
labelNodeCache.clear();
106+
104107
@SuppressWarnings("unchecked") // ¯\_(ツ)_/¯
105108
Neo4jPersistentEntity<R> rootNodeDescription = (Neo4jPersistentEntity<R>) nodeDescriptionStore.getNodeDescription(targetType);
106109
MapAccessor queryRoot = determineQueryRoot(mapAccessor, rootNodeDescription);
@@ -658,10 +661,13 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
658661

659662
private Collection<Node> extractMatchingNodes(Collection<Node> allNodesInResult, String targetLabel) {
660663

661-
Predicate<Node> onlyWithMatchingLabels = n -> n.hasLabel(targetLabel);
662-
return allNodesInResult.stream()
663-
.filter(onlyWithMatchingLabels)
664-
.collect(Collectors.toList());
664+
return labelNodeCache.computeIfAbsent(targetLabel, (label) -> {
665+
666+
Predicate<Node> onlyWithMatchingLabels = n -> n.hasLabel(label);
667+
return allNodesInResult.stream()
668+
.filter(onlyWithMatchingLabels)
669+
.collect(Collectors.toList());
670+
});
665671
}
666672

667673
private Collection<Node> extractNodes(MapAccessor allValues) {

src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java

+7
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
8585

8686
private final Lazy<Boolean> isRelationshipPropertiesEntity;
8787

88+
private final Lazy<List<NodeDescription<?>>> childNodeDescriptionsInHierarchy;
89+
8890
DefaultNeo4jPersistentEntity(TypeInformation<T> information) {
8991
super(information);
9092

@@ -95,6 +97,7 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
9597
.filter(Neo4jPersistentProperty::isDynamicLabels).findFirst().orElse(null));
9698
this.isRelationshipPropertiesEntity = Lazy.of(() -> isAnnotationPresent(RelationshipProperties.class));
9799
this.idDescription = Lazy.of(this::computeIdDescription);
100+
this.childNodeDescriptionsInHierarchy = Lazy.of(this::computeChildNodeDescriptionInHierarchy);
98101
}
99102

100103
/*
@@ -513,6 +516,10 @@ public void addChildNodeDescription(NodeDescription<?> child) {
513516

514517
@Override
515518
public List<NodeDescription<?>> getChildNodeDescriptionsInHierarchy() {
519+
return childNodeDescriptionsInHierarchy.get();
520+
}
521+
522+
private List<NodeDescription<?>> computeChildNodeDescriptionInHierarchy() {
516523
List<NodeDescription<?>> childNodes = new ArrayList<>(childNodeDescriptions);
517524

518525
for (NodeDescription<?> childNodeDescription : childNodeDescriptions) {

src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescriptionStore.java

+57-19
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,12 @@
1818
import java.lang.reflect.Modifier;
1919
import java.util.Collection;
2020
import java.util.Collections;
21-
import java.util.Comparator;
2221
import java.util.HashMap;
2322
import java.util.HashSet;
2423
import java.util.List;
2524
import java.util.Map;
26-
import java.util.Optional;
2725
import java.util.Set;
28-
import java.util.function.Function;
29-
import java.util.stream.Collectors;
26+
import java.util.function.BiFunction;
3027

3128
import org.springframework.data.mapping.context.AbstractMappingContext;
3229
import org.springframework.lang.Nullable;
@@ -46,6 +43,20 @@ final class NodeDescriptionStore {
4643
*/
4744
private final Map<String, NodeDescription<?>> nodeDescriptionsByPrimaryLabel = new HashMap<>();
4845

46+
private final Map<NodeDescription<?>, Map<List<String>, NodeDescriptionAndLabels>> nodeDescriptionAndLabelsCache = new HashMap<>();
47+
48+
private final BiFunction<NodeDescription<?>, List<String>, NodeDescriptionAndLabels> nodeDescriptionAndLabels =
49+
(nodeDescription, labels) -> {
50+
Map<List<String>, NodeDescriptionAndLabels> listNodeDescriptionAndLabelsMap = nodeDescriptionAndLabelsCache.computeIfAbsent(nodeDescription, k -> new HashMap<>());
51+
52+
NodeDescriptionAndLabels nodeDescriptionAndLabels = listNodeDescriptionAndLabelsMap.get(labels);
53+
if (nodeDescriptionAndLabels == null) {
54+
nodeDescriptionAndLabels = computeConcreteNodeDescription(nodeDescription, labels);
55+
listNodeDescriptionAndLabelsMap.put(labels, nodeDescriptionAndLabels);
56+
}
57+
return nodeDescriptionAndLabels;
58+
};
59+
4960
public boolean containsKey(String primaryLabel) {
5061
return nodeDescriptionsByPrimaryLabel.containsKey(primaryLabel);
5162
}
@@ -81,7 +92,11 @@ public NodeDescription<?> getNodeDescription(Class<?> targetType) {
8192
return null;
8293
}
8394

84-
public NodeDescriptionAndLabels deriveConcreteNodeDescription(Neo4jPersistentEntity<?> entityDescription, List<String> labels) {
95+
public NodeDescriptionAndLabels deriveConcreteNodeDescription(NodeDescription<?> entityDescription, List<String> labels) {
96+
return nodeDescriptionAndLabels.apply(entityDescription, labels);
97+
}
98+
99+
private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription<?> entityDescription, List<String> labels) {
85100

86101
boolean isConcreteClassThatFulfillsEverything = !Modifier.isAbstract(entityDescription.getUnderlyingClass().getModifiers()) && entityDescription.getStaticLabels().containsAll(labels);
87102

@@ -97,25 +112,48 @@ public NodeDescriptionAndLabels deriveConcreteNodeDescription(Neo4jPersistentEnt
97112
}
98113

99114
if (!haystack.isEmpty()) {
100-
Function<NodeDescription<?>, Integer> count = (nodeDescription) -> Math.toIntExact(nodeDescription.getStaticLabels().stream().filter(labels::contains).count());
101-
Optional<Map.Entry<NodeDescription<?>, Integer>> mostMatchingNodeDescription = haystack.stream()
102-
.filter(nd -> labels.containsAll(nd.getStaticLabels())) // remove candidates having more mandatory labels
103-
.collect(Collectors.toMap(Function.identity(), nodeDescription -> count.apply(nodeDescription)))
104-
.entrySet().stream()
105-
.max(Comparator.comparingInt(Map.Entry::getValue));
106-
107-
if (mostMatchingNodeDescription.isPresent()) {
108-
NodeDescription<?> childNodeDescription = mostMatchingNodeDescription.get().getKey();
109-
List<String> staticLabels = childNodeDescription.getStaticLabels();
110-
Set<String> surplusLabels = new HashSet<>(labels);
111-
surplusLabels.removeAll(staticLabels);
112-
return new NodeDescriptionAndLabels(childNodeDescription, surplusLabels);
115+
116+
NodeDescription<?> mostMatchingNodeDescription = null;
117+
Map<NodeDescription<?>, Integer> unmatchedLabelsCache = new HashMap<>();
118+
List<String> mostMatchingStaticLabels = null;
119+
120+
// Remove is faster than "stream, filter, count".
121+
BiFunction<NodeDescription<?>, List<String>, Integer> unmatchedLabelsCount =
122+
(nodeDescription, staticLabels) -> {
123+
Set<String> staticLabelsClone = new HashSet<>(staticLabels);
124+
labels.forEach(staticLabelsClone::remove);
125+
return staticLabelsClone.size();
126+
};
127+
128+
for (NodeDescription<?> nd : haystack) {
129+
List<String> staticLabels = nd.getStaticLabels();
130+
131+
if (staticLabels.containsAll(labels)) {
132+
Set<String> surplusLabels = new HashSet<>(labels);
133+
staticLabels.forEach(surplusLabels::remove);
134+
return new NodeDescriptionAndLabels(nd, surplusLabels);
135+
}
136+
137+
unmatchedLabelsCache.put(nd, unmatchedLabelsCount.apply(nd, staticLabels));
138+
if (mostMatchingNodeDescription == null) {
139+
mostMatchingNodeDescription = nd;
140+
mostMatchingStaticLabels = staticLabels;
141+
continue;
142+
}
143+
144+
if (unmatchedLabelsCache.get(nd) < unmatchedLabelsCache.get(mostMatchingNodeDescription)) {
145+
mostMatchingNodeDescription = nd;
146+
}
113147
}
148+
149+
Set<String> surplusLabels = new HashSet<>(labels);
150+
mostMatchingStaticLabels.forEach(surplusLabels::remove);
151+
return new NodeDescriptionAndLabels(mostMatchingNodeDescription, surplusLabels);
114152
}
115153

116154
Set<String> surplusLabels = new HashSet<>(labels);
117155
surplusLabels.remove(entityDescription.getPrimaryLabel());
118-
surplusLabels.removeAll(entityDescription.getAdditionalLabels());
156+
entityDescription.getAdditionalLabels().forEach(surplusLabels::remove);
119157
return new NodeDescriptionAndLabels(entityDescription, surplusLabels);
120158
}
121159
}

0 commit comments

Comments
 (0)