Add @Id and friends.
This commit is contained in:
26
etc/adr/adr-001.adoc
Normal file
26
etc/adr/adr-001.adoc
Normal file
@@ -0,0 +1,26 @@
|
||||
== ADR 1: Configuration of Id-Mapping
|
||||
|
||||
=== Status
|
||||
|
||||
accepted
|
||||
|
||||
=== Context
|
||||
|
||||
SDN RX needs to provide a configuration of Ids mappings.
|
||||
Ids can either be internal (native Neo4j) Ids or generated Ids.
|
||||
|
||||
=== Decision
|
||||
|
||||
The configuration should not be ambiguous.
|
||||
`@org.springframework.data.annotation.Id` will be used as the marker for an Id.
|
||||
A _strategy_ will be used to decide whether the annotated attribute will be mapped to `id(node)` or set from the external.
|
||||
The strategy will either be `internal`, `assigned` or `generated`.
|
||||
`generated` will require an additional attribute of `generator`.
|
||||
|
||||
The internal strategy will be the default.
|
||||
|
||||
To configure the Id strategy, a meta-annotated `@Id` annotation will be provided through `org.springframework.data.neo4j.core.schema.Id`
|
||||
|
||||
=== Consequences
|
||||
|
||||
We are still compatible with the OGM 3.1+ approach of recommending `@Id long id;` while providing a clear direction for the user.
|
||||
@@ -20,7 +20,9 @@ package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.SimpleAssociationHandler;
|
||||
@@ -28,6 +30,8 @@ import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.neo4j.core.schema.Id;
|
||||
import org.springframework.data.neo4j.core.schema.IdDescription;
|
||||
import org.springframework.data.neo4j.core.schema.NodeDescription;
|
||||
import org.springframework.data.neo4j.core.schema.PropertyDescription;
|
||||
import org.springframework.data.neo4j.core.schema.Relationship;
|
||||
@@ -118,7 +122,19 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
|
||||
}
|
||||
});
|
||||
|
||||
return new NodeDescription(entity.getPrimaryLabel(), properties, relationships);
|
||||
final Neo4jPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
final Optional<Id> optionalIdAnnotation = Optional
|
||||
.ofNullable(AnnotatedElementUtils.findMergedAnnotation(idProperty.getField(), Id.class));
|
||||
final IdDescription idDescription = optionalIdAnnotation
|
||||
.map(idAnnotation -> new IdDescription(idAnnotation.strategy(), idAnnotation.generator()))
|
||||
.orElseGet(() -> new IdDescription());
|
||||
|
||||
return NodeDescription.builder()
|
||||
.primaryLabel(entity.getPrimaryLabel())
|
||||
.idDescription(idDescription)
|
||||
.properties(properties)
|
||||
.relationships(relationships)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2019 "Neo4j,"
|
||||
* Neo4j Sweden AB [http://neo4j.com]
|
||||
*
|
||||
* This file is part of Neo4j.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.core.schema;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
|
||||
/**
|
||||
* Annotation to configure assigment of ids.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@Documented
|
||||
@Inherited
|
||||
@org.springframework.data.annotation.Id
|
||||
@API(status = API.Status.STABLE, since = "1.0")
|
||||
public @interface Id {
|
||||
|
||||
enum Strategy {
|
||||
|
||||
/**
|
||||
* The default, use Neo4js internal ids.
|
||||
*/
|
||||
INTERNAL,
|
||||
|
||||
/**
|
||||
* Use assigned values.
|
||||
*/
|
||||
ASSIGNED,
|
||||
|
||||
/**
|
||||
* Use generated values, generator class is required.
|
||||
*/
|
||||
GENERATED
|
||||
}
|
||||
|
||||
Strategy strategy() default Strategy.INTERNAL;
|
||||
|
||||
Class<? extends IdGenerator> generator() default IdDescription.NoopIdGenerator.class;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2019 "Neo4j,"
|
||||
* Neo4j Sweden AB [http://neo4j.com]
|
||||
*
|
||||
* This file is part of Neo4j.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.core.schema;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
|
||||
/**
|
||||
* Description howto generate Ids for entities.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@API(status = API.Status.INTERNAL, since = "1.0")
|
||||
public final class IdDescription {
|
||||
|
||||
private final Id.Strategy idStrategy;
|
||||
|
||||
private final Class<? extends IdGenerator> idGeneratorClass;
|
||||
|
||||
public IdDescription() {
|
||||
this(Id.Strategy.INTERNAL, NoopIdGenerator.class);
|
||||
}
|
||||
|
||||
public IdDescription(Id.Strategy idStrategy, Class<? extends IdGenerator> idGeneratorClass) {
|
||||
this.idStrategy = idStrategy;
|
||||
this.idGeneratorClass = idGeneratorClass;
|
||||
}
|
||||
|
||||
public Id.Strategy getIdStrategy() {
|
||||
return idStrategy;
|
||||
}
|
||||
|
||||
public Class<? extends IdGenerator> getIdGeneratorClass() {
|
||||
return idGeneratorClass;
|
||||
}
|
||||
|
||||
static class NoopIdGenerator implements IdGenerator {
|
||||
|
||||
@Override
|
||||
public Object generateId(Object entity) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2019 "Neo4j,"
|
||||
* Neo4j Sweden AB [http://neo4j.com]
|
||||
*
|
||||
* This file is part of Neo4j.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.core.schema;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
|
||||
/**
|
||||
* Interface for generating ids for entities.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@API(status = API.Status.STABLE, since = "1.0")
|
||||
public interface IdGenerator {
|
||||
|
||||
/**
|
||||
* Generates a new id for given entity
|
||||
*
|
||||
* @param entity the entity to be saved
|
||||
* @return id to be assigned to the entity
|
||||
*/
|
||||
Object generateId(Object entity);
|
||||
}
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.schema;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -43,16 +45,20 @@ public final class NodeDescription {
|
||||
*/
|
||||
private final String primaryLabel;
|
||||
|
||||
private final IdDescription idDescription;
|
||||
|
||||
private final Map<String, PropertyDescription> propertiesByFieldName;
|
||||
|
||||
private final List<RelationshipDescription> relationships;
|
||||
|
||||
public NodeDescription(String primaryLabel, List<PropertyDescription> properties,
|
||||
@Builder
|
||||
private NodeDescription(String primaryLabel, IdDescription idDescription,
|
||||
List<PropertyDescription> properties,
|
||||
List<RelationshipDescription> relationships) {
|
||||
this.primaryLabel = primaryLabel;
|
||||
this.idDescription = idDescription;
|
||||
this.propertiesByFieldName = properties.stream()
|
||||
.collect(Collectors.toMap(PropertyDescription::getFieldName, Function
|
||||
.identity()));
|
||||
.collect(Collectors.toMap(PropertyDescription::getFieldName, Function.identity()));
|
||||
this.relationships = new ArrayList<>(relationships);
|
||||
}
|
||||
|
||||
@@ -63,6 +69,10 @@ public final class NodeDescription {
|
||||
return primaryLabel;
|
||||
}
|
||||
|
||||
public IdDescription getIdDescription() {
|
||||
return idDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The properties of this node
|
||||
*/
|
||||
|
||||
@@ -18,15 +18,12 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.schema;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@API(status = API.Status.INTERNAL, since = "1.0")
|
||||
@Getter
|
||||
public final class PropertyDescription {
|
||||
|
||||
private final String fieldName;
|
||||
@@ -41,4 +38,12 @@ public final class PropertyDescription {
|
||||
this.fieldName = fieldName;
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public String getPropertyName() {
|
||||
return propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.neo4j.core.schema.Id;
|
||||
import org.springframework.data.neo4j.core.schema.Node;
|
||||
import org.springframework.data.neo4j.core.schema.NodeDescription;
|
||||
import org.springframework.data.neo4j.core.schema.Property;
|
||||
@@ -51,29 +52,39 @@ class Neo4jMappingContextTest {
|
||||
Optional<NodeDescription> optionalUserNodeDescription = schema.getNodeDescription("User");
|
||||
assertThat(optionalUserNodeDescription)
|
||||
.isPresent()
|
||||
.hasValueSatisfying(userNodeDescription -> {
|
||||
assertThat(userNodeDescription.getProperties())
|
||||
.extracting(PropertyDescription::getFieldName)
|
||||
.containsExactlyInAnyOrder("name", "first_name");
|
||||
.hasValueSatisfying(description -> {
|
||||
assertThat(description.getIdDescription().getIdStrategy())
|
||||
.isEqualTo(Id.Strategy.INTERNAL);
|
||||
|
||||
assertThat(userNodeDescription.getProperties())
|
||||
assertThat(description.getProperties())
|
||||
.extracting(PropertyDescription::getFieldName)
|
||||
.containsExactlyInAnyOrder("id", "name", "first_name");
|
||||
|
||||
assertThat(description.getProperties())
|
||||
.extracting(PropertyDescription::getPropertyName)
|
||||
.containsExactlyInAnyOrder("name", "firstName");
|
||||
.containsExactlyInAnyOrder("id", "name", "firstName");
|
||||
});
|
||||
|
||||
Optional<NodeDescription> optionalBikeNodeDescription = schema.getNodeDescription("BikeNode");
|
||||
assertThat(optionalBikeNodeDescription)
|
||||
.isPresent()
|
||||
.hasValueSatisfying(bikeNodeDescription ->
|
||||
assertThat(bikeNodeDescription.getRelationships())
|
||||
.hasValueSatisfying(description -> {
|
||||
assertThat(description.getIdDescription().getIdStrategy())
|
||||
.isEqualTo(Id.Strategy.ASSIGNED);
|
||||
|
||||
assertThat(description.getRelationships())
|
||||
.containsExactlyInAnyOrder(
|
||||
new RelationshipDescription("owner", "User"),
|
||||
new RelationshipDescription("renter", "User")));
|
||||
new RelationshipDescription("renter", "User"));
|
||||
});
|
||||
}
|
||||
|
||||
@Node("User")
|
||||
static class UserNode {
|
||||
|
||||
@org.springframework.data.annotation.Id
|
||||
private long id;
|
||||
|
||||
@Relationship(type = "OWNS", inverse = "owner")
|
||||
List<BikeNode> bikes;
|
||||
|
||||
@@ -85,10 +96,11 @@ class Neo4jMappingContextTest {
|
||||
|
||||
static class BikeNode {
|
||||
|
||||
@Id(strategy = Id.Strategy.ASSIGNED)
|
||||
private String id;
|
||||
|
||||
UserNode owner;
|
||||
|
||||
List<UserNode> renter;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ class SchemaTest {
|
||||
|
||||
@Test
|
||||
void shouldGetNodeDescription() {
|
||||
NodeDescription description = new NodeDescription("aLabel", Collections.emptyList(), Collections.emptyList());
|
||||
NodeDescription description = NodeDescription.builder().primaryLabel("aLabel")
|
||||
.properties(Collections.emptyList()).relationships(Collections.emptyList()).build();
|
||||
|
||||
Schema schema = new Schema().registerNodeDescription(description);
|
||||
assertThat(schema.getNodeDescription("aLabel")).isPresent().contains(description);
|
||||
|
||||
Reference in New Issue
Block a user