@@ -641,6 +641,9 @@ public final class Neo4jTemplate implements Neo4jOperations, FluentNeo4jOperatio
|
||||
|
||||
// create context to bundle parameters
|
||||
NestedRelationshipContext relationshipContext = NestedRelationshipContext.of(association, propertyAccessor, sourceEntity);
|
||||
if (relationshipContext.isReadOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object rawValue = relationshipContext.getValue();
|
||||
Collection<?> relatedValuesToStore = MappingSupport.unifyRelationshipValue(relationshipContext.getInverse(),
|
||||
|
||||
@@ -725,8 +725,10 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Rea
|
||||
sourceEntity.doWithAssociations((AssociationHandler<Neo4jPersistentProperty>) association -> {
|
||||
|
||||
// create context to bundle parameters
|
||||
NestedRelationshipContext relationshipContext = NestedRelationshipContext.of(association, parentPropertyAccessor,
|
||||
sourceEntity);
|
||||
NestedRelationshipContext relationshipContext = NestedRelationshipContext.of(association, parentPropertyAccessor, sourceEntity);
|
||||
if (relationshipContext.isReadOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object rawValue = relationshipContext.getValue();
|
||||
Collection<?> relatedValuesToStore = MappingSupport.unifyRelationshipValue(relationshipContext.getInverse(),
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.neo4j.driver.types.Relationship;
|
||||
import org.neo4j.driver.types.Type;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
@@ -184,7 +185,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
nodeDescription.doWithProperties((Neo4jPersistentProperty p) -> {
|
||||
|
||||
// Skip the internal properties, we don't want them to end up stored as properties
|
||||
if (p.isInternalIdProperty() || p.isDynamicLabels() || p.isEntity() || p.isVersionProperty()) {
|
||||
if (p.isInternalIdProperty() || p.isDynamicLabels() || p.isEntity() || p.isVersionProperty() || p.isAnnotationPresent(ReadOnlyProperty.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
@@ -53,6 +54,10 @@ public final class NestedRelationshipContext {
|
||||
this.inverseValueIsEmpty = inverseValueIsEmpty;
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return inverse.isAnnotationPresent(ReadOnlyProperty.class);
|
||||
}
|
||||
|
||||
public Neo4jPersistentProperty getInverse() {
|
||||
return inverse;
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.integration.issues.gh2289;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
import org.neo4j.driver.Transaction;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -47,6 +49,10 @@ class GH2289IT {
|
||||
Transaction transaction = session.beginTransaction();
|
||||
) {
|
||||
transaction.run("MATCH (n) detach delete n");
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
transaction.run("CREATE (s:SKU_RO {number: $i, name: $n})",
|
||||
Values.parameters("i", i, "n", new String(new char[] { (char) ('A' + i) })));
|
||||
}
|
||||
transaction.commit();
|
||||
bookmarkCapture.seedWith(session.lastBookmark());
|
||||
}
|
||||
@@ -64,20 +70,53 @@ class GH2289IT {
|
||||
a.rangeRelationTo(d, 1, 1, RelationType.MULTIPLICATIVE);
|
||||
a = skuRepo.save(a);
|
||||
|
||||
Assertions.assertThat(a.getRangeRelationsOut()).hasSize(3);
|
||||
assertThat(a.getRangeRelationsOut()).hasSize(3);
|
||||
b = skuRepo.findById(b.getId()).get();
|
||||
Assertions.assertThat(b.getRangeRelationsIn()).hasSize(1);
|
||||
assertThat(b.getRangeRelationsIn()).hasSize(1);
|
||||
|
||||
b.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
|
||||
b = skuRepo.save(b);
|
||||
Assertions.assertThat(b.getRangeRelationsIn()).hasSize(1);
|
||||
Assertions.assertThat(b.getRangeRelationsOut()).hasSize(1);
|
||||
assertThat(b.getRangeRelationsIn()).hasSize(1);
|
||||
assertThat(b.getRangeRelationsOut()).hasSize(1);
|
||||
}
|
||||
|
||||
@RepeatedTest(5) // GH-2294
|
||||
void testNewRelationRo(@Autowired SkuRORepository skuRepo) {
|
||||
SkuRO a = skuRepo.findOneByName("A");
|
||||
SkuRO b = skuRepo.findOneByName("B");
|
||||
SkuRO c = skuRepo.findOneByName("C");
|
||||
SkuRO d = skuRepo.findOneByName("D");
|
||||
|
||||
a.rangeRelationTo(b, 1, 1, RelationType.MULTIPLICATIVE);
|
||||
a.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
|
||||
a.rangeRelationTo(d, 1, 1, RelationType.MULTIPLICATIVE);
|
||||
a.setName("a new name");
|
||||
a = skuRepo.save(a);
|
||||
assertThat(a.getRangeRelationsOut()).hasSize(3);
|
||||
assertThat(a.getName()).isEqualTo("a new name");
|
||||
|
||||
assertThat(skuRepo.findOneByName("a new name")).isNull();
|
||||
|
||||
b = skuRepo.findOneByName("B");
|
||||
assertThat(b.getRangeRelationsIn()).hasSize(1);
|
||||
assertThat(b.getRangeRelationsOut()).hasSizeLessThanOrEqualTo(1);
|
||||
|
||||
b.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
|
||||
b = skuRepo.save(b);
|
||||
assertThat(b.getRangeRelationsIn()).hasSize(1);
|
||||
assertThat(b.getRangeRelationsOut()).hasSize(1);
|
||||
}
|
||||
|
||||
@Repository
|
||||
public interface SkuRepository extends Neo4jRepository<Sku, Long> {
|
||||
}
|
||||
|
||||
@Repository
|
||||
public interface SkuRORepository extends Neo4jRepository<SkuRO, Long> {
|
||||
|
||||
SkuRO findOneByName(String name);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@EnableNeo4jRepositories(considerNestedRepositories = true)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2011-2021 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.gh2289;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import org.springframework.data.neo4j.core.schema.GeneratedValue;
|
||||
import org.springframework.data.neo4j.core.schema.Id;
|
||||
import org.springframework.data.neo4j.core.schema.Property;
|
||||
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
|
||||
import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Data // lombok
|
||||
@RelationshipProperties
|
||||
public class RangeRelationRO {
|
||||
|
||||
@EqualsAndHashCode.Exclude
|
||||
@Id @GeneratedValue private Long id;
|
||||
|
||||
@Property private double minDelta;
|
||||
@Property private double maxDelta;
|
||||
@Property private RelationType relationType;
|
||||
|
||||
@TargetNode private SkuRO targetSku;
|
||||
|
||||
public RangeRelationRO(SkuRO targetSku, double minDelta, double maxDelta, RelationType relationType) {
|
||||
this.targetSku = targetSku;
|
||||
this.minDelta = minDelta;
|
||||
this.maxDelta = maxDelta;
|
||||
this.relationType = relationType;
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,21 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.integration.issues.gh2289;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
import org.neo4j.driver.Transaction;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -54,6 +57,10 @@ class ReactiveGH2289IT {
|
||||
Transaction transaction = session.beginTransaction();
|
||||
) {
|
||||
transaction.run("MATCH (n) detach delete n");
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
transaction.run("CREATE (s:SKU_RO {number: $i, name: $n})",
|
||||
Values.parameters("i", i, "n", new String(new char[] { (char) ('A' + i) })));
|
||||
}
|
||||
transaction.commit();
|
||||
bookmarkCapture.seedWith(session.lastBookmark());
|
||||
}
|
||||
@@ -84,7 +91,7 @@ class ReactiveGH2289IT {
|
||||
.verifyComplete();
|
||||
|
||||
skuRepo.findById(bId.get())
|
||||
.doOnNext(b -> Assertions.assertThat(b.getRangeRelationsIn()).hasSize(1))
|
||||
.doOnNext(b -> assertThat(b.getRangeRelationsIn()).hasSize(1))
|
||||
.flatMap(b -> {
|
||||
b.rangeRelationTo(cRef.get(), 1, 1, RelationType.MULTIPLICATIVE);
|
||||
return skuRepo.save(b);
|
||||
@@ -94,10 +101,62 @@ class ReactiveGH2289IT {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@RepeatedTest(5) // GH-2294
|
||||
void testNewRelationRo(@Autowired SkuRORepository skuRepo) {
|
||||
|
||||
AtomicLong bId = new AtomicLong();
|
||||
AtomicReference<SkuRO> cRef = new AtomicReference<>();
|
||||
skuRepo.findOneByName("A")
|
||||
.zipWith(skuRepo.findOneByName("B"))
|
||||
.zipWith(skuRepo.findOneByName("C"))
|
||||
.zipWith(skuRepo.findOneByName("D"))
|
||||
.flatMap(t -> {
|
||||
SkuRO a = t.getT1().getT1().getT1();
|
||||
SkuRO b = t.getT1().getT1().getT2();
|
||||
SkuRO c = t.getT1().getT2();
|
||||
SkuRO d = t.getT2();
|
||||
|
||||
bId.set(b.getId());
|
||||
cRef.set(c);
|
||||
a.rangeRelationTo(b, 1, 1, RelationType.MULTIPLICATIVE);
|
||||
a.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
|
||||
a.rangeRelationTo(d, 1, 1, RelationType.MULTIPLICATIVE);
|
||||
|
||||
a.setName("a new name");
|
||||
|
||||
return skuRepo.save(a);
|
||||
}).as(StepVerifier::create)
|
||||
.expectNextMatches(a -> a.getRangeRelationsOut().size() == 3 && "a new name".equals(a.getName()))
|
||||
.verifyComplete();
|
||||
|
||||
skuRepo.findOneByName("a new name")
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
|
||||
skuRepo.findOneByName("B")
|
||||
.doOnNext(b -> {
|
||||
assertThat(b.getRangeRelationsIn()).hasSize(1);
|
||||
assertThat(b.getRangeRelationsOut()).hasSizeLessThanOrEqualTo(1);
|
||||
})
|
||||
.flatMap(b -> {
|
||||
b.rangeRelationTo(cRef.get(), 1, 1, RelationType.MULTIPLICATIVE);
|
||||
return skuRepo.save(b);
|
||||
})
|
||||
.as(StepVerifier::create)
|
||||
.expectNextMatches(b -> b.getRangeRelationsIn().size() == 1 && b.getRangeRelationsOut().size() == 1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Repository
|
||||
public interface SkuRepository extends ReactiveNeo4jRepository<Sku, Long> {
|
||||
}
|
||||
|
||||
@Repository
|
||||
public interface SkuRORepository extends ReactiveNeo4jRepository<SkuRO, Long> {
|
||||
|
||||
Mono<SkuRO> findOneByName(String name);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@EnableReactiveNeo4jRepositories(considerNestedRepositories = true)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2011-2021 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.gh2289;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
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.Property;
|
||||
import org.springframework.data.neo4j.core.schema.Relationship;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Node("SKU_RO")
|
||||
@Getter // lombok
|
||||
@Setter
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
public class SkuRO {
|
||||
|
||||
@Id @GeneratedValue
|
||||
@EqualsAndHashCode.Include
|
||||
private Long id;
|
||||
|
||||
@Property("number")
|
||||
@EqualsAndHashCode.Include
|
||||
private Long number;
|
||||
|
||||
@ReadOnlyProperty
|
||||
@Property("name")
|
||||
@EqualsAndHashCode.Include
|
||||
private String name;
|
||||
|
||||
@Relationship(type = "RANGE_RELATION_TO", direction = Relationship.Direction.OUTGOING)
|
||||
private Set<RangeRelationRO> rangeRelationsOut = new HashSet<>();
|
||||
|
||||
@ReadOnlyProperty
|
||||
@Relationship(type = "RANGE_RELATION_TO", direction = Relationship.Direction.INCOMING)
|
||||
private Set<RangeRelationRO> rangeRelationsIn = new HashSet<>();
|
||||
|
||||
public SkuRO(Long number, String name) {
|
||||
this.number = number;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public RangeRelationRO rangeRelationTo(SkuRO sku, double minDelta, double maxDelta, RelationType relationType) {
|
||||
RangeRelationRO relationOut = new RangeRelationRO(sku, minDelta, maxDelta, relationType);
|
||||
rangeRelationsOut.add(relationOut);
|
||||
return relationOut;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2011-2021 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.gh2289;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
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.Property;
|
||||
import org.springframework.data.neo4j.core.schema.Relationship;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Node("SKU_RO")
|
||||
@Getter // lombok
|
||||
@Setter
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
public class SkuRO {
|
||||
|
||||
@Id @GeneratedValue
|
||||
@EqualsAndHashCode.Include
|
||||
private Long id;
|
||||
|
||||
@Property("number")
|
||||
@EqualsAndHashCode.Include
|
||||
private Long number;
|
||||
|
||||
@ReadOnlyProperty
|
||||
@Property("name")
|
||||
@EqualsAndHashCode.Include
|
||||
private String name;
|
||||
|
||||
@Relationship(type = "RANGE_RELATION_TO", direction = Relationship.Direction.OUTGOING)
|
||||
private Set<RangeRelationRO> rangeRelationsOut = new HashSet<>();
|
||||
|
||||
@ReadOnlyProperty
|
||||
@Relationship(type = "RANGE_RELATION_TO", direction = Relationship.Direction.INCOMING)
|
||||
private Set<RangeRelationRO> rangeRelationsIn = new HashSet<>();
|
||||
|
||||
public SkuRO(Long number, String name) {
|
||||
this.number = number;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public RangeRelationRO rangeRelationTo(SkuRO sku, double minDelta, double maxDelta, RelationType relationType) {
|
||||
RangeRelationRO relationOut = new RangeRelationRO(sku, minDelta, maxDelta, relationType);
|
||||
rangeRelationsOut.add(relationOut);
|
||||
return relationOut;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user