diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jEntityConverter.java b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jEntityConverter.java index 98c36bf43..9b3e4ba97 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jEntityConverter.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jEntityConverter.java @@ -313,14 +313,6 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter { String internalId = IdentitySupport.getInternalId(queryResult, direction); Supplier mappedObjectSupplier = () -> { - if (knownObjects.isInCreation(internalId)) { - throw new MappingException( - String.format( - "The node with id %s has a logical cyclic mapping dependency; " + - "its creation caused the creation of another node that has a reference to this", - internalId.substring(1)) - ); - } knownObjects.setInCreation(internalId); List allLabels = getLabels(queryResult, nodeDescription); @@ -945,6 +937,14 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter { } try { read.lock(); + if (isInCreation(internalId)) { + throw new MappingException( + String.format( + "The node with id %s has a logical cyclic mapping dependency; " + + "its creation caused the creation of another node that has a reference to this", + internalId.substring(1)) + ); + } return internalIdStore.get(internalId); } finally { read.unlock(); diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java index 33116cbda..c062016e5 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java @@ -15,6 +15,7 @@ */ package org.springframework.data.neo4j.integration.issues; +import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.tuple; @@ -57,6 +58,7 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; +import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.neo4j.core.DatabaseSelectionProvider; import org.springframework.data.neo4j.core.Neo4jTemplate; @@ -128,6 +130,8 @@ import org.springframework.data.neo4j.integration.issues.gh2579.TableNode; import org.springframework.data.neo4j.integration.issues.gh2579.TableRepository; import org.springframework.data.neo4j.integration.issues.gh2583.GH2583Node; import org.springframework.data.neo4j.integration.issues.gh2583.GH2583Repository; +import org.springframework.data.neo4j.integration.issues.gh2622.GH2622Repository; +import org.springframework.data.neo4j.integration.issues.gh2622.MePointingTowardsMe; import org.springframework.data.neo4j.integration.issues.gh2639.Company; import org.springframework.data.neo4j.integration.issues.gh2639.CompanyPerson; import org.springframework.data.neo4j.integration.issues.gh2639.CompanyRepository; @@ -991,6 +995,20 @@ class IssuesIT extends TestBase { ); } + @Test + @Tag("GH-2622") + void throwCyclicMappingDependencyExceptionOnSelfReference(@Autowired GH2622Repository repository) { + MePointingTowardsMe entity = new MePointingTowardsMe("me", new ArrayList<>()); + entity.others.add(entity); + repository.save(entity); + + assertThatExceptionOfType(MappingException.class).isThrownBy(repository::findAll) + .withRootCauseInstanceOf(MappingException.class) + .extracting(Throwable::getCause, as(InstanceOfAssertFactories.THROWABLE)) + .hasMessageContaining("has a logical cyclic mapping dependency"); + + } + @Configuration @EnableTransactionManagement @EnableNeo4jRepositories(namedQueriesLocation = "more-custom-queries.properties") diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2622/GH2622Repository.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2622/GH2622Repository.java new file mode 100644 index 000000000..0976e9f7a --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2622/GH2622Repository.java @@ -0,0 +1,23 @@ +/* + * Copyright 2011-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2622; + +import org.springframework.data.neo4j.repository.Neo4jRepository; + +/** + * @author Gerrit Meier + */ +public interface GH2622Repository extends Neo4jRepository { } diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2622/MePointingTowardsMe.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2622/MePointingTowardsMe.java new file mode 100644 index 000000000..ebe95040b --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2622/MePointingTowardsMe.java @@ -0,0 +1,61 @@ +/* + * Copyright 2011-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2622; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; + +import java.util.List; +import java.util.Objects; + +/** + * @author Gerrit Meier + */ +@Node +public class MePointingTowardsMe { + + @Id + @GeneratedValue + Long id; + + final String name; + + @Relationship + public final List others; + + public MePointingTowardsMe(String name, List others) { + this.name = name; + this.others = others; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MePointingTowardsMe that = (MePointingTowardsMe) o; + return name.equals(that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +}