Skip to content

Commit dec5533

Browse files
committed
GH-3036 - Use correct type for mapping.
If there is a class hierarchy scenario, SDN will try to use the best matching child class. In some cases the parent, non-abstract class is already the best fit and should be used. Closes #3036 Signed-off-by: Gerrit Meier <meistermeier@gmail.com>
1 parent 47d8a98 commit dec5533

File tree

6 files changed

+177
-5
lines changed

6 files changed

+177
-5
lines changed

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ NodeDescriptionAndLabels deriveConcreteNodeDescription(NodeDescription<?> entity
108108
private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription<?> entityDescription,
109109
List<String> labels) {
110110

111-
boolean isConcreteClassThatFulfillsEverything = !Modifier
112-
.isAbstract(entityDescription.getUnderlyingClass().getModifiers())
111+
var isAbstractClassOrInterface = Modifier.isAbstract(entityDescription.getUnderlyingClass().getModifiers());
112+
boolean isConcreteClassThatFulfillsEverything = !isAbstractClassOrInterface
113113
&& entityDescription.getStaticLabels().containsAll(labels);
114114

115115
if (labels == null || labels.isEmpty() || isConcreteClassThatFulfillsEverything) {
@@ -126,9 +126,13 @@ private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription<
126126

127127
if (!haystack.isEmpty()) {
128128

129-
NodeDescription<?> mostMatchingNodeDescription = null;
129+
NodeDescription<?> mostMatchingNodeDescription = !isAbstractClassOrInterface ? entityDescription : null;
130+
List<String> mostMatchingStaticLabels = !isAbstractClassOrInterface ? entityDescription.getStaticLabels()
131+
: List.of();
130132
Map<NodeDescription<?>, Integer> unmatchedLabelsCache = new HashMap<>();
131-
List<String> mostMatchingStaticLabels = null;
133+
if (!isAbstractClassOrInterface) {
134+
unmatchedLabelsCache.put(mostMatchingNodeDescription, labels.size() - mostMatchingStaticLabels.size());
135+
}
132136

133137
for (NodeDescription<?> nd : haystack) {
134138

@@ -164,7 +168,7 @@ private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription<
164168
}
165169

166170
Set<String> surplusLabels = new HashSet<>(labels);
167-
if (mostMatchingStaticLabels != null) {
171+
if (!mostMatchingStaticLabels.isEmpty()) {
168172
mostMatchingStaticLabels.forEach(surplusLabels::remove);
169173
}
170174
if (mostMatchingNodeDescription == null) {

src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@
199199
import org.springframework.data.neo4j.integration.issues.gh2973.RelationshipB;
200200
import org.springframework.data.neo4j.integration.issues.gh2973.RelationshipC;
201201
import org.springframework.data.neo4j.integration.issues.gh2973.RelationshipD;
202+
import org.springframework.data.neo4j.integration.issues.gh3036.Vehicle;
203+
import org.springframework.data.neo4j.integration.issues.gh3036.VehicleRepository;
202204
import org.springframework.data.neo4j.integration.issues.qbe.A;
203205
import org.springframework.data.neo4j.integration.issues.qbe.ARepository;
204206
import org.springframework.data.neo4j.integration.issues.qbe.B;
@@ -1854,6 +1856,23 @@ void abstractedRelationshipTypesShouldBeMappedCorrectly(@Autowired Gh2973Reposit
18541856
assertThat(relationshipsCFail.get(0)).isNotExactlyInstanceOf(BaseRelationship.class);
18551857
}
18561858

1859+
@Tag("GH-3036")
1860+
@Test
1861+
void asdf(@Autowired VehicleRepository repository) {
1862+
var vehicleWithoutDynamicLabels = new Vehicle();
1863+
var vehicleWithOneDynamicLabel = new Vehicle();
1864+
vehicleWithOneDynamicLabel.setLabels(Set.of("label1"));
1865+
var vehicleWithTwoDynamicLabels = new Vehicle();
1866+
vehicleWithTwoDynamicLabels.setLabels(Set.of("label1", "label2"));
1867+
1868+
repository
1869+
.saveAll(List.of(vehicleWithoutDynamicLabels, vehicleWithOneDynamicLabel, vehicleWithTwoDynamicLabels));
1870+
1871+
var vehicles = repository.findAllVehicles();
1872+
assertThat(vehicles).hasOnlyElementsOfType(Vehicle.class);
1873+
1874+
}
1875+
18571876
@Configuration
18581877
@EnableTransactionManagement
18591878
@EnableNeo4jRepositories(namedQueriesLocation = "more-custom-queries.properties")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2011-2025 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+
* https://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.neo4j.integration.issues.gh3036;
17+
18+
import org.springframework.data.neo4j.core.schema.Node;
19+
20+
/**
21+
* Detailed implementation candidate
22+
*/
23+
@Node(primaryLabel = "Car")
24+
public class Car extends Vehicle {
25+
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2011-2025 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+
* https://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.neo4j.integration.issues.gh3036;
17+
18+
import org.springframework.data.neo4j.core.schema.Node;
19+
20+
/**
21+
* Detailed implementation candidate
22+
*/
23+
@Node(primaryLabel = "Sedan")
24+
public class Sedan extends Car {
25+
26+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2011-2025 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+
* https://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.neo4j.integration.issues.gh3036;
17+
18+
import java.util.Set;
19+
20+
import org.springframework.data.neo4j.core.schema.DynamicLabels;
21+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
22+
import org.springframework.data.neo4j.core.schema.Id;
23+
import org.springframework.data.neo4j.core.schema.Node;
24+
import org.springframework.data.neo4j.core.schema.Property;
25+
26+
/**
27+
* Parent class
28+
*/
29+
@Node(primaryLabel = "Vehicle")
30+
public class Vehicle {
31+
32+
@Id
33+
@GeneratedValue
34+
public String id;
35+
36+
@Property(name = "name")
37+
public String name;
38+
39+
@DynamicLabels
40+
public Set<String> labels = Set.of();
41+
42+
public String getId() {
43+
return this.id;
44+
}
45+
46+
public void setId(String id) {
47+
this.id = id;
48+
}
49+
50+
public Set<String> getLabels() {
51+
return this.labels;
52+
}
53+
54+
public void setLabels(Set<String> labels) {
55+
this.labels = labels;
56+
}
57+
58+
public String getName() {
59+
return this.name;
60+
}
61+
62+
public void setName(String name) {
63+
this.name = name;
64+
}
65+
66+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2011-2025 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+
* https://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.neo4j.integration.issues.gh3036;
17+
18+
import java.util.List;
19+
20+
import org.springframework.data.neo4j.repository.Neo4jRepository;
21+
import org.springframework.data.neo4j.repository.query.Query;
22+
23+
/**
24+
* Repository for testing GH-3036
25+
*/
26+
public interface VehicleRepository extends Neo4jRepository<Vehicle, String> {
27+
28+
@Query("MATCH (vehicle:Vehicle) RETURN vehicle")
29+
List<Vehicle> findAllVehicles();
30+
31+
}

0 commit comments

Comments
 (0)