Skip to content

Commit c4a3f1f

Browse files
committed
GH-2622 - Throw exception on immutable self-reference.
This slipped through the check before: A has dependency on A defined within the constructor. Because of a "same" entity check, we skipped the `inCreation` check and did not throw the MappingException. Closes #2622
1 parent 28cc45a commit c4a3f1f

File tree

4 files changed

+110
-8
lines changed

4 files changed

+110
-8
lines changed

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

+8-8
Original file line numberDiff line numberDiff line change
@@ -313,14 +313,6 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
313313
String internalId = IdentitySupport.getInternalId(queryResult, direction);
314314

315315
Supplier<ET> mappedObjectSupplier = () -> {
316-
if (knownObjects.isInCreation(internalId)) {
317-
throw new MappingException(
318-
String.format(
319-
"The node with id %s has a logical cyclic mapping dependency; " +
320-
"its creation caused the creation of another node that has a reference to this",
321-
internalId.substring(1))
322-
);
323-
}
324316
knownObjects.setInCreation(internalId);
325317

326318
List<String> allLabels = getLabels(queryResult, nodeDescription);
@@ -945,6 +937,14 @@ private Object getObject(@Nullable String internalId) {
945937
}
946938
try {
947939
read.lock();
940+
if (isInCreation(internalId)) {
941+
throw new MappingException(
942+
String.format(
943+
"The node with id %s has a logical cyclic mapping dependency; " +
944+
"its creation caused the creation of another node that has a reference to this",
945+
internalId.substring(1))
946+
);
947+
}
948948
return internalIdStore.get(internalId);
949949
} finally {
950950
read.unlock();

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

+18
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.neo4j.integration.issues;
1717

18+
import static org.assertj.core.api.Assertions.as;
1819
import static org.assertj.core.api.Assertions.assertThat;
1920
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2021
import static org.assertj.core.api.Assertions.tuple;
@@ -57,6 +58,7 @@
5758
import org.springframework.data.domain.Page;
5859
import org.springframework.data.domain.PageRequest;
5960
import org.springframework.data.domain.Sort;
61+
import org.springframework.data.mapping.MappingException;
6062
import org.springframework.data.mapping.PersistentPropertyAccessor;
6163
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
6264
import org.springframework.data.neo4j.core.Neo4jTemplate;
@@ -128,6 +130,8 @@
128130
import org.springframework.data.neo4j.integration.issues.gh2579.TableRepository;
129131
import org.springframework.data.neo4j.integration.issues.gh2583.GH2583Node;
130132
import org.springframework.data.neo4j.integration.issues.gh2583.GH2583Repository;
133+
import org.springframework.data.neo4j.integration.issues.gh2622.GH2622Repository;
134+
import org.springframework.data.neo4j.integration.issues.gh2622.MePointingTowardsMe;
131135
import org.springframework.data.neo4j.integration.issues.gh2639.Company;
132136
import org.springframework.data.neo4j.integration.issues.gh2639.CompanyPerson;
133137
import org.springframework.data.neo4j.integration.issues.gh2639.CompanyRepository;
@@ -991,6 +995,20 @@ void relationshipsOfGenericRelationshipsGetResolvedCorrectly(@Autowired CompanyR
991995
);
992996
}
993997

998+
@Test
999+
@Tag("GH-2622")
1000+
void throwCyclicMappingDependencyExceptionOnSelfReference(@Autowired GH2622Repository repository) {
1001+
MePointingTowardsMe entity = new MePointingTowardsMe("me", new ArrayList<>());
1002+
entity.others.add(entity);
1003+
repository.save(entity);
1004+
1005+
assertThatExceptionOfType(MappingException.class).isThrownBy(repository::findAll)
1006+
.withRootCauseInstanceOf(MappingException.class)
1007+
.extracting(Throwable::getCause, as(InstanceOfAssertFactories.THROWABLE))
1008+
.hasMessageContaining("has a logical cyclic mapping dependency");
1009+
1010+
}
1011+
9941012
@Configuration
9951013
@EnableTransactionManagement
9961014
@EnableNeo4jRepositories(namedQueriesLocation = "more-custom-queries.properties")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2011-2023 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.gh2622;
17+
18+
import org.springframework.data.neo4j.repository.Neo4jRepository;
19+
20+
/**
21+
* @author Gerrit Meier
22+
*/
23+
public interface GH2622Repository extends Neo4jRepository<MePointingTowardsMe, Long> { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2011-2023 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.gh2622;
17+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
18+
import org.springframework.data.neo4j.core.schema.Id;
19+
import org.springframework.data.neo4j.core.schema.Node;
20+
import org.springframework.data.neo4j.core.schema.Relationship;
21+
22+
import java.util.List;
23+
import java.util.Objects;
24+
25+
/**
26+
* @author Gerrit Meier
27+
*/
28+
@Node
29+
public class MePointingTowardsMe {
30+
31+
@Id
32+
@GeneratedValue
33+
Long id;
34+
35+
final String name;
36+
37+
@Relationship
38+
public final List<MePointingTowardsMe> others;
39+
40+
public MePointingTowardsMe(String name, List<MePointingTowardsMe> others) {
41+
this.name = name;
42+
this.others = others;
43+
}
44+
45+
@Override
46+
public boolean equals(Object o) {
47+
if (this == o) {
48+
return true;
49+
}
50+
if (o == null || getClass() != o.getClass()) {
51+
return false;
52+
}
53+
MePointingTowardsMe that = (MePointingTowardsMe) o;
54+
return name.equals(that.name);
55+
}
56+
57+
@Override
58+
public int hashCode() {
59+
return Objects.hash(name);
60+
}
61+
}

0 commit comments

Comments
 (0)