Skip to content

Commit 01fa719

Browse files
committed
GH-2199 - More relaxed inheritance mapping.
1 parent 6c59559 commit 01fa719

File tree

8 files changed

+141
-22
lines changed

8 files changed

+141
-22
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ public <R> R read(Class<R> targetType, MapAccessor mapAccessor) {
111111

112112
private <R> MapAccessor determineQueryRoot(MapAccessor mapAccessor, Neo4jPersistentEntity<R> rootNodeDescription) {
113113

114-
String primaryLabel = rootNodeDescription.getPrimaryLabel();
114+
List<String> primaryLabels = new ArrayList<>();
115+
primaryLabels.add(rootNodeDescription.getPrimaryLabel());
116+
rootNodeDescription.getChildNodeDescriptionsInHierarchy().forEach(nodeDescription -> primaryLabels.add(nodeDescription.getPrimaryLabel()));
115117

116118
// Massage the initial mapAccessor into something we can deal with
117119
Iterable<Value> recordValues = mapAccessor instanceof Value && ((Value) mapAccessor).hasType(nodeType) ?
@@ -123,7 +125,7 @@ private <R> MapAccessor determineQueryRoot(MapAccessor mapAccessor, Neo4jPersist
123125
for (Value value : recordValues) {
124126
if (value.hasType(nodeType)) { // It is a node
125127
Node node = value.asNode();
126-
if (node.hasLabel(primaryLabel)) { // it has a matching label
128+
if (primaryLabels.stream().anyMatch(node::hasLabel)) { // it has a matching label
127129
// We haven't seen this node yet, so we take it
128130
if (knownObjects.getObject(node.id()) == null) {
129131
matchingNodes.add(node);

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

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,6 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
6565

6666
private static final LogAccessor log = new LogAccessor(LogFactory.getLog(DefaultNeo4jPersistentProperty.class));
6767

68-
/**
69-
* If an entity is annotated with {@link Node}, we consider this as an explicit entity that should get validated more
70-
* strictly.
71-
*/
72-
private final Boolean isExplicitEntity;
73-
7468
/**
7569
* The label that describes the label most concrete.
7670
*/
@@ -96,7 +90,6 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
9690
DefaultNeo4jPersistentEntity(TypeInformation<T> information) {
9791
super(information);
9892

99-
this.isExplicitEntity = this.isAnnotationPresent(Node.class) || this.isAnnotationPresent(Persistent.class);
10093
this.primaryLabel = computePrimaryLabel();
10194
this.additionalLabels = Lazy.of(this::computeAdditionalLabels);
10295
this.graphProperties = Lazy.of(this::computeGraphProperties);
@@ -213,7 +206,9 @@ public void verify() {
213206

214207
private void verifyIdDescription() {
215208

216-
if (this.getIdDescription() == null && isExplicitEntity) {
209+
if (this.getIdDescription() == null
210+
&& (this.isAnnotationPresent(Node.class) || this.isAnnotationPresent(Persistent.class))) {
211+
217212
throw new IllegalStateException("Missing id property on " + this.getUnderlyingClass() + ".");
218213
}
219214
}
@@ -354,12 +349,16 @@ private List<String> computeOwnAdditionalLabels() {
354349
@NonNull
355350
private List<String> computeParentLabels() {
356351
List<String> parentLabels = new ArrayList<>();
357-
NodeDescription<?> parentNodeDescriptionCalculated = parentNodeDescription;
352+
Neo4jPersistentEntity<?> parentNodeDescriptionCalculated = (Neo4jPersistentEntity<?>) parentNodeDescription;
358353

359354
while (parentNodeDescriptionCalculated != null) {
360-
parentLabels.add(parentNodeDescriptionCalculated.getPrimaryLabel());
361-
parentLabels.addAll(parentNodeDescriptionCalculated.getAdditionalLabels());
362-
parentNodeDescriptionCalculated = parentNodeDescriptionCalculated.getParentNodeDescription();
355+
if (parentNodeDescriptionCalculated.isAnnotationPresent(Node.class)
356+
|| parentNodeDescriptionCalculated.isAnnotationPresent(Persistent.class)) {
357+
358+
parentLabels.add(parentNodeDescriptionCalculated.getPrimaryLabel());
359+
parentLabels.addAll(parentNodeDescriptionCalculated.getAdditionalLabels());
360+
}
361+
parentNodeDescriptionCalculated = (Neo4jPersistentEntity<?>) parentNodeDescriptionCalculated.getParentNodeDescription();
363362
}
364363
return parentLabels;
365364
}

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverterFactory;
4949
import org.springframework.data.neo4j.core.convert.Neo4jSimpleTypes;
5050
import org.springframework.data.neo4j.core.schema.IdGenerator;
51-
import org.springframework.data.neo4j.core.schema.Node;
5251
import org.springframework.data.util.ReflectionUtils;
5352
import org.springframework.data.util.TypeInformation;
5453
import org.springframework.lang.Nullable;
@@ -209,10 +208,7 @@ private boolean isValidParentNode(@Nullable Class<?> parentClass) {
209208
return false;
210209
}
211210

212-
boolean isExplicitNode = parentClass.isAnnotationPresent(Node.class);
213-
boolean isAbstractClass = Modifier.isAbstract(parentClass.getModifiers());
214-
215-
return isExplicitNode && isAbstractClass;
211+
return Modifier.isAbstract(parentClass.getModifiers());
216212
}
217213

218214
/*

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,11 @@ public interface Neo4jPersistentEntity<T>
4242
*/
4343
Optional<Neo4jPersistentProperty> getDynamicLabelsProperty();
4444

45+
/**
46+
* Determines if the entity is annotated with {@link org.springframework.data.neo4j.core.schema.RelationshipProperties}
47+
*
48+
* @return true if this is a relationship properties class, otherwise false.
49+
*/
4550
boolean isRelationshipPropertiesEntity();
51+
4652
}

src/test/java/org/springframework/data/neo4j/integration/imperative/InheritanceMappingIT.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,21 @@
1919
import org.junit.jupiter.api.Test;
2020
import org.neo4j.driver.Driver;
2121
import org.neo4j.driver.Session;
22+
import org.neo4j.driver.Transaction;
2223
import org.springframework.beans.factory.annotation.Autowired;
2324
import org.springframework.context.annotation.Bean;
2425
import org.springframework.context.annotation.Configuration;
2526
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
27+
import org.springframework.data.neo4j.integration.shared.common.AbstractPet;
28+
import org.springframework.data.neo4j.integration.shared.common.Cat;
29+
import org.springframework.data.neo4j.integration.shared.common.Dog;
2630
import org.springframework.data.neo4j.integration.shared.common.Inheritance;
2731
import org.springframework.data.neo4j.repository.Neo4jRepository;
2832
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
33+
import org.springframework.data.neo4j.repository.query.Query;
2934
import org.springframework.data.neo4j.test.Neo4jExtension;
3035
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
31-
import org.springframework.stereotype.Repository;
36+
import org.springframework.data.repository.query.Param;
3237
import org.springframework.transaction.annotation.EnableTransactionManagement;
3338

3439
import java.util.Collection;
@@ -129,10 +134,34 @@ void resultCollectionShouldHaveCorrectTypes(@Autowired TerritoryRepository repos
129134
);
130135
}
131136

132-
@Repository
137+
@Test // GH-2199
138+
void findAndMapAllConcreteSubclassesWithoutParentLabel(@Autowired PetsRepository petsRepository) {
139+
140+
try (Session session = driver.session()) {
141+
Transaction transaction = session.beginTransaction();
142+
transaction.run("CREATE (:Cat{name:'a'})");
143+
transaction.run("CREATE (:Cat{name:'a'})");
144+
transaction.run("CREATE (:Cat{name:'a'})");
145+
transaction.run("CREATE (:Dog{name:'a'})");
146+
transaction.commit();
147+
}
148+
149+
List<AbstractPet> pets = petsRepository.findPets("a");
150+
assertThat(pets)
151+
.hasOnlyElementsOfType(AbstractPet.class)
152+
.hasAtLeastOneElementOfType(Dog.class)
153+
.hasAtLeastOneElementOfType(Cat.class);
154+
}
155+
156+
interface PetsRepository extends Neo4jRepository<AbstractPet, Long> {
157+
158+
@Query("MATCH (n {name: $name}) RETURN n")
159+
List<AbstractPet> findPets(@Param("name") String name);
160+
161+
}
162+
133163
interface BuildingRepository extends Neo4jRepository<Inheritance.Building, Long> {}
134164

135-
@Repository
136165
interface TerritoryRepository extends Neo4jRepository<Inheritance.BaseTerritory, Long> {}
137166

138167
@Configuration
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2011-2021 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.shared.common;
17+
18+
import lombok.Getter;
19+
import lombok.Setter;
20+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
21+
import org.springframework.data.neo4j.core.schema.Id;
22+
23+
/**
24+
* @author Gerrit Meier
25+
*/
26+
public abstract class AbstractPet {
27+
28+
@Id
29+
@GeneratedValue
30+
private Long id;
31+
32+
@Getter @Setter
33+
private String name;
34+
35+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2011-2021 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.shared.common;
17+
18+
import org.springframework.data.neo4j.core.schema.Node;
19+
20+
/**
21+
* @author Gerrit Meier
22+
*/
23+
@Node(labels = "Cat")
24+
public class Cat extends AbstractPet {
25+
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2011-2021 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.shared.common;
17+
18+
import org.springframework.data.neo4j.core.schema.Node;
19+
20+
/**
21+
* @author Gerrit Meier
22+
*/
23+
@Node(labels = "Dog")
24+
public class Dog extends AbstractPet {
25+
26+
}

0 commit comments

Comments
 (0)