GH-2487 - Improve performance of NodeDescription determination.
Closes #2487
This commit is contained in:
@@ -79,6 +79,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
private final Type relationshipType;
|
||||
private final Type mapType;
|
||||
private final Type listType;
|
||||
private final Map<String, Collection<Node>> labelNodeCache = new HashMap<>();
|
||||
|
||||
DefaultNeo4jEntityConverter(EntityInstantiators entityInstantiators, Neo4jConversionService conversionService,
|
||||
NodeDescriptionStore nodeDescriptionStore, TypeSystem typeSystem) {
|
||||
@@ -103,6 +104,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
Neo4jPersistentEntity<R> rootNodeDescription = (Neo4jPersistentEntity) nodeDescriptionStore.getNodeDescription(targetType);
|
||||
knownObjects.nextRecord();
|
||||
labelNodeCache.clear();
|
||||
|
||||
MapAccessor queryRoot = determineQueryRoot(mapAccessor, rootNodeDescription);
|
||||
if (queryRoot == null) {
|
||||
@@ -644,10 +646,13 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
private Collection<Node> extractMatchingNodes(Collection<Node> allNodesInResult, String targetLabel) {
|
||||
|
||||
Predicate<Node> onlyWithMatchingLabels = n -> n.hasLabel(targetLabel);
|
||||
return allNodesInResult.stream()
|
||||
.filter(onlyWithMatchingLabels)
|
||||
.collect(Collectors.toList());
|
||||
return labelNodeCache.computeIfAbsent(targetLabel, (label) -> {
|
||||
|
||||
Predicate<Node> onlyWithMatchingLabels = n -> n.hasLabel(label);
|
||||
return allNodesInResult.stream()
|
||||
.filter(onlyWithMatchingLabels)
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
||||
private Collection<Node> extractNodes(MapAccessor allValues) {
|
||||
|
||||
@@ -85,6 +85,8 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
|
||||
private final Lazy<Boolean> isRelationshipPropertiesEntity;
|
||||
|
||||
private final Lazy<Set<NodeDescription<?>>> childNodeDescriptionsInHierarchy;
|
||||
|
||||
DefaultNeo4jPersistentEntity(TypeInformation<T> information) {
|
||||
super(information);
|
||||
|
||||
@@ -95,6 +97,7 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
.filter(Neo4jPersistentProperty::isDynamicLabels).findFirst().orElse(null));
|
||||
this.isRelationshipPropertiesEntity = Lazy.of(() -> isAnnotationPresent(RelationshipProperties.class));
|
||||
this.idDescription = Lazy.of(this::computeIdDescription);
|
||||
this.childNodeDescriptionsInHierarchy = Lazy.of(this::computeChildNodeDescriptionInHierarchy);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -513,6 +516,10 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
|
||||
@Override
|
||||
public Set<NodeDescription<?>> getChildNodeDescriptionsInHierarchy() {
|
||||
return childNodeDescriptionsInHierarchy.get();
|
||||
}
|
||||
|
||||
private Set<NodeDescription<?>> computeChildNodeDescriptionInHierarchy() {
|
||||
Set<NodeDescription<?>> childNodes = new HashSet<>(childNodeDescriptions);
|
||||
|
||||
for (NodeDescription<?> childNodeDescription : childNodeDescriptions) {
|
||||
|
||||
@@ -18,15 +18,12 @@ package org.springframework.data.neo4j.core.mapping;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -46,6 +43,24 @@ final class NodeDescriptionStore {
|
||||
*/
|
||||
private final Map<String, NodeDescription<?>> nodeDescriptionsByPrimaryLabel = new HashMap<>();
|
||||
|
||||
private final Map<NodeDescription<?>, Map<List<String>, NodeDescriptionAndLabels>> nodeDescriptionAndLabelsCache = new HashMap<>();
|
||||
|
||||
private final BiFunction<NodeDescription<?>, List<String>, NodeDescriptionAndLabels> nodeDescriptionAndLabels =
|
||||
(nodeDescription, labels) -> {
|
||||
Map<List<String>, NodeDescriptionAndLabels> listNodeDescriptionAndLabelsMap = nodeDescriptionAndLabelsCache.get(nodeDescription);
|
||||
if (listNodeDescriptionAndLabelsMap == null) {
|
||||
nodeDescriptionAndLabelsCache.put(nodeDescription, new HashMap<>());
|
||||
listNodeDescriptionAndLabelsMap = nodeDescriptionAndLabelsCache.get(nodeDescription);
|
||||
}
|
||||
|
||||
NodeDescriptionAndLabels cachedNodeDescriptionAndLabels = listNodeDescriptionAndLabelsMap.get(labels);
|
||||
if (cachedNodeDescriptionAndLabels == null) {
|
||||
cachedNodeDescriptionAndLabels = computeConcreteNodeDescription(nodeDescription, labels);
|
||||
listNodeDescriptionAndLabelsMap.put(labels, cachedNodeDescriptionAndLabels);
|
||||
}
|
||||
return cachedNodeDescriptionAndLabels;
|
||||
};
|
||||
|
||||
public boolean containsKey(String primaryLabel) {
|
||||
return nodeDescriptionsByPrimaryLabel.containsKey(primaryLabel);
|
||||
}
|
||||
@@ -81,7 +96,11 @@ final class NodeDescriptionStore {
|
||||
return null;
|
||||
}
|
||||
|
||||
public NodeDescriptionAndLabels deriveConcreteNodeDescription(Neo4jPersistentEntity<?> entityDescription, List<String> labels) {
|
||||
public NodeDescriptionAndLabels deriveConcreteNodeDescription(NodeDescription<?> entityDescription, List<String> labels) {
|
||||
return nodeDescriptionAndLabels.apply(entityDescription, labels);
|
||||
}
|
||||
|
||||
private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription<?> entityDescription, List<String> labels) {
|
||||
|
||||
boolean isConcreteClassThatFulfillsEverything = !Modifier.isAbstract(entityDescription.getUnderlyingClass().getModifiers()) && entityDescription.getStaticLabels().containsAll(labels);
|
||||
|
||||
@@ -97,25 +116,48 @@ final class NodeDescriptionStore {
|
||||
}
|
||||
|
||||
if (!haystack.isEmpty()) {
|
||||
Function<NodeDescription<?>, Integer> count = (nodeDescription) -> Math.toIntExact(nodeDescription.getStaticLabels().stream().filter(labels::contains).count());
|
||||
Optional<Map.Entry<NodeDescription<?>, Integer>> mostMatchingNodeDescription = haystack.stream()
|
||||
.filter(nd -> labels.containsAll(nd.getStaticLabels())) // remove candidates having more mandatory labels
|
||||
.collect(Collectors.toMap(Function.identity(), nodeDescription -> count.apply(nodeDescription)))
|
||||
.entrySet().stream()
|
||||
.max(Comparator.comparingInt(Map.Entry::getValue));
|
||||
|
||||
if (mostMatchingNodeDescription.isPresent()) {
|
||||
NodeDescription<?> childNodeDescription = mostMatchingNodeDescription.get().getKey();
|
||||
List<String> staticLabels = childNodeDescription.getStaticLabels();
|
||||
Set<String> surplusLabels = new HashSet<>(labels);
|
||||
surplusLabels.removeAll(staticLabels);
|
||||
return new NodeDescriptionAndLabels(childNodeDescription, surplusLabels);
|
||||
NodeDescription<?> mostMatchingNodeDescription = null;
|
||||
Map<NodeDescription<?>, Integer> unmatchedLabelsCache = new HashMap<>();
|
||||
List<String> mostMatchingStaticLabels = null;
|
||||
|
||||
// Remove is faster than "stream, filter, count".
|
||||
BiFunction<NodeDescription<?>, List<String>, Integer> unmatchedLabelsCount =
|
||||
(nodeDescription, staticLabels) -> {
|
||||
Set<String> staticLabelsClone = new HashSet<>(staticLabels);
|
||||
labels.forEach(staticLabelsClone::remove);
|
||||
return staticLabelsClone.size();
|
||||
};
|
||||
|
||||
for (NodeDescription<?> nd : haystack) {
|
||||
List<String> staticLabels = nd.getStaticLabels();
|
||||
|
||||
if (staticLabels.containsAll(labels)) {
|
||||
Set<String> surplusLabels = new HashSet<>(labels);
|
||||
staticLabels.forEach(surplusLabels::remove);
|
||||
return new NodeDescriptionAndLabels(nd, surplusLabels);
|
||||
}
|
||||
|
||||
unmatchedLabelsCache.put(nd, unmatchedLabelsCount.apply(nd, staticLabels));
|
||||
if (mostMatchingNodeDescription == null) {
|
||||
mostMatchingNodeDescription = nd;
|
||||
mostMatchingStaticLabels = staticLabels;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unmatchedLabelsCache.get(nd) < unmatchedLabelsCache.get(mostMatchingNodeDescription)) {
|
||||
mostMatchingNodeDescription = nd;
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> surplusLabels = new HashSet<>(labels);
|
||||
mostMatchingStaticLabels.forEach(surplusLabels::remove);
|
||||
return new NodeDescriptionAndLabels(mostMatchingNodeDescription, surplusLabels);
|
||||
}
|
||||
|
||||
Set<String> surplusLabels = new HashSet<>(labels);
|
||||
surplusLabels.remove(entityDescription.getPrimaryLabel());
|
||||
surplusLabels.removeAll(entityDescription.getAdditionalLabels());
|
||||
entityDescription.getAdditionalLabels().forEach(surplusLabels::remove);
|
||||
return new NodeDescriptionAndLabels(entityDescription, surplusLabels);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user