Permalink
Browse files

DATAGRAPH-219, DATAGRAPH-210 unique relationship, and documentation f…

…or unique entities
  • Loading branch information...
1 parent 3221213 commit caed15e2b728779ba1d111f571b386ecbc95aa1d @jexp jexp committed Apr 19, 2012
@@ -15,7 +15,7 @@
*/
@NodeEntity
public class Person {
- @Indexed
+ @Indexed(unique=true)
String id;
@Indexed(indexType=IndexType.FULLTEXT, indexName = "people")
String name;
@@ -8,7 +8,7 @@
@NodeEntity
public class Person {
- @Indexed
+ @Indexed(unique=true)
String id;
@Indexed(indexType=IndexType.FULLTEXT, indexName = "people")
String name;
@@ -15,7 +15,7 @@
@NodeEntity
public class Person {
@GraphId Long nodeId;
- @Indexed
+ @Indexed(unique=true)
String id;
@Indexed(indexType=IndexType.FULLTEXT, indexName = "people")
String name;
@@ -70,7 +70,7 @@ protected Node checkAndGetNode(Object entity) {
protected void removeMissingRelationshipsInStoreAndKeepOnlyNewRelationShipsInSet(Node node, Set<Node> targetNodes) {
for (Relationship relationship : node.getRelationships(type, direction)) {
if (!targetNodes.remove(relationship.getOtherNode(node)))
- relationship.delete();
+ template.delete(relationship);
}
}
@@ -131,27 +131,36 @@ public boolean isRelationshipEntity(Class targetType) {
return (S) graphDatabase.createNode(null);
}
if (persistentEntity.isRelationshipEntity()) {
- return createRelationship(entity, persistentEntity);
+ return getOrCreateRelationship(entity, persistentEntity);
}
throw new IllegalArgumentException("The entity " + persistentEntity.getEntityName() + " has to be either annotated with @NodeEntity or @RelationshipEntity");
}
private Node createUniqueNode(Neo4jPersistentProperty uniqueProperty, Object entity) {
final IndexInfo indexInfo = uniqueProperty.getIndexInfo();
final Object value = uniqueProperty.getValueFromEntity(entity, MappingPolicy.MAP_FIELD_DIRECT_POLICY);
- if (value==null) return graphDatabase.createNode(null);
- return graphDatabase.getOrCreateNode(indexInfo.getIndexName(),indexInfo.getIndexKey(), value, Collections.<String,Object>emptyMap());
+ if (value==null) throw new MappingException("Error creating "+uniqueProperty.getOwner().getName()+" with "+entity+" unique property "+uniqueProperty.getName()+" has null value");
+ return graphDatabase.getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String,Object>emptyMap());
}
@SuppressWarnings("unchecked")
- private <S extends PropertyContainer> S createRelationship(Object entity, Neo4jPersistentEntity<?> persistentEntity) {
+ private <S extends PropertyContainer> S getOrCreateRelationship(Object entity, Neo4jPersistentEntity<?> persistentEntity) {
final RelationshipProperties relationshipProperties = persistentEntity.getRelationshipProperties();
final Neo4jPersistentProperty startNodeProperty = relationshipProperties.getStartNodeProperty();
Node startNode = (Node) getPersistentState(startNodeProperty.getValue(entity, startNodeProperty.getMappingPolicy()));
final Neo4jPersistentProperty endNodeProperty = relationshipProperties.getEndNodeProperty();
Node endNode = (Node) getPersistentState(endNodeProperty.getValue(entity, endNodeProperty.getMappingPolicy()));
RelationshipType relationshipType = getRelationshipType(persistentEntity,entity);
- return (S) startNode.createRelationshipTo(endNode, relationshipType);
+ if (persistentEntity.isUnique()) {
+ final Neo4jPersistentProperty uniqueProperty = persistentEntity.getUniqueProperty();
+ final IndexInfo indexInfo = uniqueProperty.getIndexInfo();
+ final Object value = uniqueProperty.getValueFromEntity(entity, MappingPolicy.MAP_FIELD_DIRECT_POLICY);
+ if (value == null) {
+ throw new MappingException("Error creating "+uniqueProperty.getOwner().getName()+" with "+entity+" unique property "+uniqueProperty.getName()+" has null value");
+ }
+ return (S) graphDatabase.getOrCreateRelationship(indexInfo.getIndexName(),indexInfo.getIndexKey(), value, startNode,endNode,relationshipType.name(), Collections.<String,Object>emptyMap());
+ }
+ return (S) graphDatabase.createRelationship(startNode, endNode, relationshipType, Collections.<String,Object>emptyMap());
}
private RelationshipType getRelationshipType(Neo4jPersistentEntity persistentEntity, Object entity) {
@@ -206,7 +215,7 @@ public RelationshipResult createRelationshipBetween(Object source, Object target
public RelationshipResult removeRelationshipTo(Object source, Object target, String relationshipType) {
final Relationship relationship = getRelationshipBetween(source, target, relationshipType);
if (relationship!=null) {
- relationship.delete();
+ graphDatabase.remove(relationship);
return new RelationshipResult(relationship, RelationshipResult.Type.DELETED);
}
return null;
@@ -37,6 +37,7 @@
final Class<?> entityType = entity.getClass();
@SuppressWarnings("unchecked") final Neo4jPersistentEntity<Object> persistentEntity =
(Neo4jPersistentEntity<Object>) mappingContext.getPersistentEntity(entityType);
+
NodeEntityState nodeEntityState = new NodeEntityState(null, entity, entityType, template,
nodeDelegatingFieldAccessorFactory, persistentEntity);
if (!detachable) {
@@ -17,13 +17,22 @@
import org.junit.Test;
import org.mockito.Mockito;
+import org.neo4j.graphdb.Direction;
+import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.PropertyContainer;
+import org.neo4j.graphdb.Relationship;
+import org.springframework.data.neo4j.model.BestFriend;
import org.springframework.data.neo4j.model.Friendship;
import org.springframework.data.neo4j.model.Person;
import org.springframework.transaction.annotation.Transactional;
+import java.util.Collections;
+
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
/**
* @author mh
@@ -0,0 +1,84 @@
+/**
+ * Copyright 2011 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
+ *
+ * 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.model;
+
+
+import org.springframework.data.neo4j.annotation.EndNode;
+import org.springframework.data.neo4j.annotation.GraphId;
+import org.springframework.data.neo4j.annotation.Indexed;
+import org.springframework.data.neo4j.annotation.RelationshipEntity;
+import org.springframework.data.neo4j.annotation.RelationshipType;
+import org.springframework.data.neo4j.annotation.StartNode;
+import org.springframework.data.neo4j.fieldaccess.DynamicProperties;
+
+import java.util.Date;
+
+@RelationshipEntity(type = "BEST_FRIEND")
+public class BestFriend {
+ @GraphId
+ private Long id;
+
+ @Indexed(unique = true)
+ private String secretName;
+
+ public BestFriend() { }
+
+ public BestFriend(Person p1, Person p2, String secretName) {
+ this.p1 = p1;
+ this.p2 = p2;
+ this.secretName = secretName;
+ }
+
+ @StartNode
+ private Person p1;
+
+ @EndNode
+ private Person p2;
+
+ public Person getPerson1() {
+ return p1;
+ }
+
+ public Person getPerson2() {
+ return p2;
+ }
+
+ public String getSecretName() {
+ return secretName;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ BestFriend friendship = (BestFriend) o;
+ if (id == null) return super.equals(o);
+ return id.equals(friendship.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return id != null ? id.hashCode() : super.hashCode();
+ }
+
+}
@@ -74,6 +74,9 @@
@RelatedTo(type = "boss", direction = Direction.INCOMING)
private Person boss;
+ @RelatedToVia
+ private BestFriend bestFriend;
+
@Fetch
@RelatedToVia(type = "knows", elementClass = Friendship.class)
private Iterable<Friendship> friendships;
@@ -292,4 +295,16 @@ public Object getDynamicProperty() {
public Person(Long graphId) {
this.graphId = graphId;
}
+
+ public void setBestFriend(Person p2, String secret) {
+ if (p2==null) {
+ this.bestFriend = null;
+ } else {
+ this.bestFriend = new BestFriend(this, p2,secret);
+ }
+ }
+
+ public BestFriend getBestFriend() {
+ return bestFriend;
+ }
}
@@ -74,6 +74,14 @@ public void shouldOnlyCreateSingleInstanceForUniqueNodeEntity() {
assertEquals(1, uniqueClubRepository.count());
}
+
+ @Test(expected = MappingException.class)
+ public void shouldFailOnNullPropertyValue() {
+ UniqueClub club = new UniqueClub();
+ club.setName(null);
+ uniqueClubRepository.save(club);
+ }
+
@Test
public void shouldOnlyCreateSingleInstanceForUniqueNumericNodeEntity() {
UniqueNumericIdClub club = new UniqueNumericIdClub();
@@ -86,6 +94,13 @@ public void shouldOnlyCreateSingleInstanceForUniqueNumericNodeEntity() {
assertEquals(1, uniqueNumericIdClubRepository.count());
}
+ @Test(expected = MappingException.class)
+ public void shouldFailOnNullNumericPropertyValue() {
+ UniqueNumericIdClub club = new UniqueNumericIdClub();
+ club.setClubId(null);
+ uniqueNumericIdClubRepository.save(club);
+ }
+
@Test
public void shouldCreateMultipleInstancesForNonUniqueNodeEntity() {
Club club = new Club();
@@ -0,0 +1,107 @@
+/**
+ * Copyright 2011 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
+ *
+ * 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.unique;
+
+import org.junit.Test;
+import org.neo4j.graphdb.Direction;
+import org.neo4j.graphdb.DynamicRelationshipType;
+import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.Relationship;
+import org.springframework.data.neo4j.mapping.Neo4jPersistentTestBase;
+import org.springframework.data.neo4j.model.BestFriend;
+import org.springframework.data.neo4j.model.Person;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+
+/**
+ * @author mh
+ * @since 18.04.12
+ */
+public class UniqueRelationshipTest extends Neo4jPersistentTestBase {
+
+ @Test
+ public void testCreateUniqueRelationship() {
+ final Person p1 = storeInGraph(michael);
+ final Person p2 = storeInGraph(andres);
+ final BestFriend bestFriend = new BestFriend(p1, p2, "cypher");
+ template.save(bestFriend);
+
+ final Relationship bestFriendRel = template.getPersistentState(bestFriend);
+ assertEquals(bestFriendRel,((Node)template.getPersistentState(michael)).getSingleRelationship(DynamicRelationshipType.withName("BEST_FRIEND"), Direction.OUTGOING));
+ assertEquals(bestFriendRel.getEndNode(), template.getPersistentState(andres));
+ assertEquals("cypher",bestFriendRel.getProperty("secretName"));
+ assertEquals(bestFriendRel,template.getIndex(BestFriend.class).get("secretName","cypher").getSingle());
+
+ final Person p3 = storeInGraph(emil);
+ final BestFriend bestFriend2 = new BestFriend(p1, p3, "cypher");
+ template.save(bestFriend2);
+
+ final Relationship bestFriend2Rel = template.getPersistentState(bestFriend2);
+ assertEquals(bestFriend2Rel, bestFriendRel);
+ assertEquals(bestFriend2Rel.getEndNode(), template.getPersistentState(andres));
+ assertEquals(bestFriendRel,template.getIndex(BestFriend.class).get("secretName","cypher").getSingle());
+ }
+
+ @Test
+ public void testCreateUniqueRelationshipRelatedToVia() {
+ final Person p1 = storeInGraph(michael);
+ final Person p2 = storeInGraph(andres);
+ p1.setBestFriend(p2,"cypher");
+ template.save(p1);
+ final BestFriend bestFriend = p1.getBestFriend();
+
+ final Relationship bestFriendRel = template.getPersistentState(bestFriend);
+ assertEquals(bestFriendRel,((Node)template.getPersistentState(michael)).getSingleRelationship(DynamicRelationshipType.withName("BEST_FRIEND"), Direction.OUTGOING));
+ assertEquals(bestFriendRel.getEndNode(), template.getPersistentState(andres));
+ assertEquals("cypher",bestFriendRel.getProperty("secretName"));
+ assertEquals(bestFriendRel,template.getIndex(BestFriend.class).get("secretName","cypher").getSingle());
+
+ final Person p3 = storeInGraph(emil);
+ p1.setBestFriend(p3,"cypher");
+ final BestFriend bestFriend2 = p1.getBestFriend();
+ template.save(bestFriend2);
+
+ final Relationship bestFriend2Rel = template.getPersistentState(bestFriend2);
+ assertEquals(bestFriend2Rel, bestFriendRel);
+ assertEquals(bestFriend2Rel.getEndNode(), template.getPersistentState(andres));
+ assertEquals(bestFriendRel,template.getIndex(BestFriend.class).get("secretName","cypher").getSingle());
+
+ p1.setBestFriend(null,null);
+ template.save(p1);
+ assertEquals(null, ((Node) template.getPersistentState(michael)).getSingleRelationship(DynamicRelationshipType.withName("BEST_FRIEND"), Direction.OUTGOING));
+ p1.setBestFriend(p3, "cypher");
+ template.save(p1);
+ final BestFriend bestFriend3 = p1.getBestFriend();
+
+ final Relationship bestFriend3Rel = template.getPersistentState(bestFriend3);
+ assertNotSame(bestFriend3Rel, bestFriendRel);
+ assertEquals(bestFriend3Rel.getEndNode(), template.getPersistentState(emil));
+ assertEquals(bestFriend3Rel, template.getIndex(BestFriend.class).get("secretName", "cypher").getSingle());
+ }
+
+ @Test
+ public void testDeleteAndRecreateUniqueRelationship() {
+ final Node n1 = template.createNode();
+ final Node n2 = template.createNode();
+ final Relationship r1 = template.getOrCreateRelationship("test", "key", "value", n1, n2, "type", null);
+ template.delete(r1);
+ final Node n3 = template.createNode();
+ final Relationship r2 = template.getOrCreateRelationship("test", "key", "value", n1, n3, "type", null);
+ assertFalse("r1 is returned although being deleted", r1.equals(r2));
+ }
+}
@@ -118,13 +118,21 @@ Person mark = graphRepository.findAllByQuery("people-search", "name", "ma*");
<title>Unique indexes</title>
<para>
Unique indexing with <code>index.putIfAbsent</code> and <code>UniqueFactory</code> was introduced in Neo4j 1.6.
- In Spring Data Neo4j this is made available via <code>Neo4jTemplate.getOrCreateNode</code>.
-
+ It is also available via the REST API.
+ In Spring Data Neo4j this is made available via <code>Neo4jTemplate.getOrCreateNode</code> and
+ <code>Neo4jTemplate.getOrCreateRelationship</code>.
</para>
<para>In an entity at most one field can be annotated with <code>@Indexed(unique=true)</code> regardless of the index-type used.
The uniqueness will be taken into account when creating the entity by reusing an existing entity if that unique key-combination
already exists. On saving of the field it will be cross-checked against the index and fail with a DataIntegrityViolationException
- if the field was changed to an already existing unique value.
+ if the field was changed to an already existing unique value. Null values are no longer allowed for these properties.
+ </para>
+ <para>
+ <note>
+ This works for both Node-Entities as well as Relationship-Entities. Relationship-Uniqueness in Neo4j is global so that
+ a existing unique instance of this relationship may connect two completely different nodes and might also have a
+ different type.
+ </note>
</para>
<para>
<example>
Oops, something went wrong.

0 comments on commit caed15e

Please sign in to comment.