DATAGRAPH-445: Added @Indexed(unique=true, failOnDuplicate=[true|false]) functionality

This commit is contained in:
Nicki Watt
2014-04-06 20:16:10 +01:00
parent 29e33b36bd
commit 0a8bf79da6
21 changed files with 602 additions and 19 deletions

View File

@@ -0,0 +1,11 @@
package org.springframework.data.neo4j.aspects;
import org.springframework.data.neo4j.aspects.support.domain.Account1;
import org.springframework.data.neo4j.repository.GraphRepository;
/**
* @author Nicki Watt
* @since 06-04-2014
*/
public interface Account1Repository extends GraphRepository<Account1> {
}

View File

@@ -0,0 +1,12 @@
package org.springframework.data.neo4j.aspects;
import org.springframework.data.neo4j.aspects.support.domain.Account2;
import org.springframework.data.neo4j.repository.GraphRepository;
/**
* @author Nicki Watt
* @since 06-04-2014
*/
public interface Account2Repository extends GraphRepository<Account2> {
}

View File

@@ -24,9 +24,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.neo4j.aspects.FriendshipRepository;
import org.springframework.data.neo4j.aspects.GroupRepository;
import org.springframework.data.neo4j.aspects.PersonRepository;
import org.springframework.data.neo4j.aspects.*;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.mapping.StoredEntityType;
import org.springframework.data.neo4j.support.node.Neo4jHelper;
@@ -48,6 +46,8 @@ public class EntityTestBase {
@Autowired protected GraphDatabaseService graphDatabaseService;
@Autowired protected Account1Repository account1Repository;
@Autowired protected Account2Repository account2Repository;
@Autowired protected PersonRepository personRepository;
@Autowired protected GroupRepository groupRepository;
@Autowired protected FriendshipRepository friendshipRepository;

View File

@@ -20,18 +20,20 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.ConstraintViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.aspects.Group;
import org.springframework.data.neo4j.aspects.Person;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.data.neo4j.aspects.core.NodeBacked;
import org.springframework.data.neo4j.aspects.support.domain.Account1;
import org.springframework.data.neo4j.aspects.support.domain.Account2;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
@@ -40,9 +42,10 @@ import java.util.Map;
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.neo4j.helpers.collection.IteratorUtil.addToCollection;
import static org.neo4j.helpers.collection.IteratorUtil.asCollection;
@@ -123,4 +126,66 @@ public class GraphRepositoryTests extends EntityTestBase {
Group team = personRepository.findTeam(testTeam.michael);
assertThat(team, is(testTeam.sdg));
}
@Test
@Transactional
public void testSaveWhenFailOnDuplicateSetToFalse() {
// Account1
// @Indexed(unique = true, failOnDuplicate = false)
// private String accountNumber;
Account1 acc1 = new Account1("111-222-333", "Mr George - Current Account 1");
Account1 acc2 = new Account1("111-222-333", "Mr George - Current Account 2");
Account1 savedAcc1 = account1Repository.save(acc1);
Account1 savedAcc2 = account1Repository.save(acc2);
assertEquals("expecting the saving of the same entity result in a merge of nodes", ((NodeBacked)savedAcc1).getNodeId(), ((NodeBacked)savedAcc2).getNodeId());
assertEquals("Mr George - Current Account 2", savedAcc2.getName() );
Account1 loadedAcc1 = account1Repository.findBySchemaPropertyValue("accountNumber", "111-222-333");
assertEquals("Mr George - Current Account 2", loadedAcc1.getName() );
}
@Test(expected = ConstraintViolationException.class)
@Transactional
public void testSaveWhenFailOnDuplicateSetToTrue() {
// Account2
// @Indexed(unique = true, failOnDuplicate = true)
// private String accountNumber;
Account2 acc1 = new Account2("111-222-333", "Mr George - Current Account 1");
Account2 acc2 = new Account2("111-222-333", "Mr George - Current Account 2");
Account2 savedAcc1 = account2Repository.save(acc1);
Account2 savedAcc2 = account2Repository.save(acc2);
}
@Test
@Transactional
public void testSaveWhenDefaultFailOnDuplicateSetToTrueAllowsUpdates() {
// Account2
// @Indexed(unique = true, failOnDuplicate = true)
// private String accountNumber;
Account2 acc1 = new Account2("111-222-333", "Mr George - Current Account 1");
Account2 savedAcc1 = account2Repository.save(acc1);
acc1.setName("Mr George - Current Account 2");
account2Repository.save(savedAcc1);
// No exception expected!
}
/*
@Ignore("Not catering for explicit overrides at present")
@Test(expected = ConstraintViolationException.class)
@Transactional
public void testSaveWithOverrideFailOnDuplicateSetToTrue() {
// Account1
// @Indexed(unique = true, failOnDuplicate = false)
// private String accountNumber;
Account1 acc1 = new Account1("111-222-333", "Mr George - Current Account 1");
Account1 acc2 = new Account1("111-222-333", "Mr George - Current Account 2");
Account1 savedAcc1 = account1Repository.save(acc1,true);
Account1 savedAcc2 = account1Repository.save(acc2,true);
}
*/
}

View File

@@ -20,6 +20,7 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.ConstraintViolationException;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Transaction;
import org.neo4j.helpers.collection.IteratorUtil;
@@ -27,6 +28,8 @@ import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.neo4j.aspects.Attribute;
import org.springframework.data.neo4j.aspects.Group;
import org.springframework.data.neo4j.aspects.Person;
import org.springframework.data.neo4j.aspects.support.domain.Account1;
import org.springframework.data.neo4j.aspects.support.domain.Account2;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -45,6 +48,7 @@ import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.springframework.data.neo4j.aspects.Person.persistedPerson;
@RunWith(SpringJUnit4ClassRunner.class)
@@ -270,4 +274,44 @@ public class NodeEntityTests extends EntityTestBase {
g.getReadOnlyPersons();
g.persist();
}
@Test @Transactional
public void testDefaultFailOnDuplicateSetToTrueSavesCorrectlyWhenUpdating() {
final String UPDATED_NAME = "Mr George - Current Account 2";
Account2 acc1 = new Account2("111-222-333", "Mr George - Current Account 1");
acc1.persist();
acc1.setName(UPDATED_NAME);
acc1.persist(); // This should save fine
Account2 loadedAcc1 = account2Repository.findBySchemaPropertyValue("accountNumber","111-222-333");
assertNotNull(loadedAcc1);
assertEquals("Expected name to have been updated (merged) with last value",UPDATED_NAME,loadedAcc1.getName());
}
@Test(expected = ConstraintViolationException.class) @Transactional
public void testDefaultFailOnDuplicateSetToTrueCausesExceptionWhenAnotherDuplicateEntityCreated() {
Account2 acc1 = new Account2("111-222-333", "Mr George - Current Account 1");
Account2 acc2 = new Account2("111-222-333", "Mr George - Current Account 2");
acc1.persist();
acc2.persist(); // This should cause an exception
}
@Test @Transactional
public void testDefaultFailOnDuplicateSetToFalseCausesMergeWhenAnotherDuplicateEntityCreated() {
final String UPDATED_NAME = "Mr George - Current Account 2";
Account1 acc1 = new Account1("111-222-333", "Mr George - Current Account 1");
Account1 acc2 = new Account1("111-222-333", UPDATED_NAME);
acc1.persist();
Long acc1NodeId = acc1.getNodeId();
acc2.persist(); // This should be merged
Long acc2NodeId = acc2.getNodeId();
assertEquals("Ids should be the same",acc1NodeId,acc2NodeId);
Account1 loadedAcc1 = account1Repository.findBySchemaPropertyValue("accountNumber","111-222-333");
assertNotNull(loadedAcc1);
assertEquals("Expected name to have been updated (merged) with last value",UPDATED_NAME,loadedAcc1.getName());
}
}

View File

@@ -0,0 +1,59 @@
/**
* 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.aspects.support.domain;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
@NodeEntity
public class Account1 {
private static final long serialVersionUID = 1L;
@Indexed(unique = true, failOnDuplicate = false)
private String accountNumber;
private String name;
public Account1() {
}
public Account1(String accountNumber, String name) {
this.accountNumber = accountNumber;
this.name = name;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,81 @@
/**
* 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.aspects.support.domain;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
@NodeEntity
public class Account2 {
private static final long serialVersionUID = 1L;
@GraphId
private Long graphId;
@Indexed(unique = true, failOnDuplicate = true)
private String accountNumber;
private String name;
public Account2() {
}
public Account2(String accountNumber, String name) {
this.accountNumber = accountNumber;
this.name = name;
}
public Long getGraphId() {
return graphId;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account2 person = (Account2) o;
if (graphId == null) return super.equals(o);
return graphId.equals(person.graphId);
}
@Override
public int hashCode() {
return graphId != null ? graphId.hashCode() : super.hashCode();
}
}

View File

@@ -115,6 +115,8 @@
<bean id="mappingContext" class="org.springframework.data.neo4j.support.mapping.Neo4jMappingContext">
<property name="initialEntitySet">
<set>
<value>org.springframework.data.neo4j.aspects.support.domain.Account1</value>
<value>org.springframework.data.neo4j.aspects.support.domain.Account2</value>
<value>org.springframework.data.neo4j.aspects.Developer</value>
<value>org.springframework.data.neo4j.aspects.Person</value>
<value>org.springframework.data.neo4j.aspects.Group</value>
@@ -168,6 +170,14 @@
</bean>
</property>
</bean>
<bean id="account2Repository" class="org.springframework.data.neo4j.repository.GraphRepositoryFactoryBean">
<property name="repositoryInterface" value="org.springframework.data.neo4j.aspects.Account2Repository" />
<property name="neo4jTemplate" ref="template"/>
</bean>
<bean id="account1Repository" class="org.springframework.data.neo4j.repository.GraphRepositoryFactoryBean">
<property name="repositoryInterface" value="org.springframework.data.neo4j.aspects.Account1Repository" />
<property name="neo4jTemplate" ref="template"/>
</bean>
<bean id="groupRepository" class="org.springframework.data.neo4j.repository.GraphRepositoryFactoryBean">
<property name="repositoryInterface" value="org.springframework.data.neo4j.aspects.GroupRepository" />
<property name="neo4jTemplate" ref="template"/>

View File

@@ -17,12 +17,11 @@
package org.springframework.data.neo4j.rest.integration;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.neo4j.aspects.support.GraphRepositoryTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.data.neo4j.support.node.Neo4jHelper;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -53,7 +52,13 @@ public class RestGraphRepositoryTests extends GraphRepositoryTests {
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
// TODO - Change REST to have a better (more descriptive)
// exception thrown when duplicate violations occur
@Test(expected = IllegalStateException.class)
public void testSaveWhenFailOnDuplicateSetToTrue() {
super.testSaveWhenFailOnDuplicateSetToTrue();
}
}

View File

@@ -16,11 +16,9 @@
package org.springframework.data.neo4j.rest.integration;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.*;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.ConstraintViolationException;
import org.springframework.data.neo4j.aspects.support.NodeEntityTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
@@ -60,4 +58,11 @@ public class RestNodeEntityTests extends NodeEntityTests {
public void testSetShortProperty() {
// super.testSetShortProperty();
}
// TODO - Change REST to have a better (more descriptive)
// exception thrown when duplicate violations occur
@Test(expected = IllegalStateException.class)
public void testDefaultFailOnDuplicateSetToTrueCausesExceptionWhenAnotherDuplicateEntityCreated() {
super.testDefaultFailOnDuplicateSetToTrueCausesExceptionWhenAnotherDuplicateEntityCreated();
}
}

View File

@@ -39,10 +39,22 @@ public @interface Indexed {
String fieldName() default "";
/**
* Indicates whether to apply a unique constraint on this property, defaults to false.
*/
boolean unique() default false;
boolean numeric() default false;
/**
* Only applicable when indexType is LABEL and unique=true, indicates how to handle attempts to save
* entities where this unique property already exists, defaults to false. When set to false, default entity
* saving behaviour resorts to merge type behaviour whilst when set to true, results in an exception being
* thrown when attempting to save another entity where the same unique indexed property already exists under
* a different node id.
*/
boolean failOnDuplicate() default false;
// FQN is a fix for javac compiler bug http://bugs.sun.com/view_bug.do?bug_id=6512707
org.springframework.data.neo4j.annotation.Indexed.Level level() default org.springframework.data.neo4j.annotation.Indexed.Level.CLASS;

View File

@@ -31,6 +31,7 @@ public class IndexInfo {
private final Indexed.Level level;
private String indexKey;
private final boolean unique;
private final boolean failOnDuplicate;
private boolean numeric;
private Indexed annotation;
private Neo4jPersistentProperty property;
@@ -43,6 +44,7 @@ public class IndexInfo {
fieldName = annotation.fieldName();
this.indexKey = fieldName.isEmpty() ? property.getNeo4jPropertyName() : fieldName;
unique = annotation.unique();
failOnDuplicate = annotation.failOnDuplicate();
level = annotation.level();
numeric = annotation.numeric();
}
@@ -114,6 +116,10 @@ public class IndexInfo {
return unique;
}
public boolean isFailOnDuplicate() {
return failOnDuplicate;
}
public boolean isNumeric() {
return numeric;
}

View File

@@ -26,9 +26,22 @@ import org.springframework.transaction.annotation.Transactional;
*/
public interface SchemaIndexRepository<T> {
/**
* Finds an entity based on the provided schema indexed property value if one exists.
* @param property The name of the schema indexed property
* @param value The value of the schema indexed property
* @return The single entity associated with this property value setting, or
* null if one does not exist.
*/
@Transactional
T findBySchemaPropertyValue(String property, Object value);
/**
* Finds all entities which have a schema indexed property set to specified value.
* @param property The name of the schema indexed property
* @param value The value of the schema indexed property
* @return A result of all entities which match indexed property value.
*/
@Transactional
Result<T> findAllBySchemaPropertyValue(String property, Object value);

View File

@@ -69,6 +69,7 @@ import java.util.Collections;
import java.util.Map;
import static java.lang.String.format;
import static org.neo4j.helpers.collection.MapUtil.map;
import static org.springframework.data.neo4j.support.ParameterCheck.notNull;
/**
@@ -738,14 +739,16 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware {
return infrastructure.getMappingContext();
}
public Node createUniqueNode(Object entity) {
public Node createUniqueNode(Object entity) {
final Neo4jPersistentEntityImpl<?> persistentEntity = getPersistentEntity(entity.getClass());
final Neo4jPersistentProperty uniqueProperty = persistentEntity.getUniqueProperty();
Object value = uniqueProperty.getValueFromEntity(entity, MappingPolicy.MAP_FIELD_DIRECT_POLICY);
if (value == null) return createNode();
final IndexInfo indexInfo = uniqueProperty.getIndexInfo();
if (indexInfo.isLabelBased()) {
return getGraphDatabase().merge(indexInfo.getIndexName(),indexInfo.getIndexKey(),value, Collections.<String,Object>emptyMap(), persistentEntity.getAllLabels());
return (indexInfo.isFailOnDuplicate())
? getGraphDatabase().createNode(map(uniqueProperty.getName(),value),persistentEntity.getAllLabels())
: getGraphDatabase().merge(indexInfo.getIndexName(),indexInfo.getIndexKey(),value, Collections.<String,Object>emptyMap(), persistentEntity.getAllLabels());
} else {
if (value instanceof Number && indexInfo.isNumeric()) value = ValueContext.numeric((Number) value);
return getGraphDatabase().getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String, Object>emptyMap(), persistentEntity.getAllLabels());

View File

@@ -35,6 +35,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.neo4j.helpers.collection.MapUtil.map;
/**
* @author mh
* @since 02.10.11
@@ -151,7 +153,9 @@ public class EntityStateHandler {
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");
if (indexInfo.isLabelBased()) {
return graphDatabase.merge(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String,Object>emptyMap(), persistentEntity.getAllLabels());
return (indexInfo.isFailOnDuplicate())
? graphDatabase.createNode(map(uniqueProperty.getName(),value),persistentEntity.getAllLabels())
: graphDatabase.merge(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String,Object>emptyMap(), persistentEntity.getAllLabels());
} else {
return graphDatabase.getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String,Object>emptyMap(),persistentEntity.getAllLabels());
}

View File

@@ -0,0 +1,81 @@
/**
* 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.GraphId;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
@NodeEntity
public class Account1 {
private static final long serialVersionUID = 1L;
@GraphId
private Long graphId;
@Indexed(unique = true, failOnDuplicate = false)
private String accountNumber;
private String name;
public Account1() {
}
public Account1(String accountNumber, String name) {
this.accountNumber = accountNumber;
this.name = name;
}
public Long getGraphId() {
return graphId;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account1 person = (Account1) o;
if (graphId == null) return super.equals(o);
return graphId.equals(person.graphId);
}
@Override
public int hashCode() {
return graphId != null ? graphId.hashCode() : super.hashCode();
}
}

View File

@@ -0,0 +1,81 @@
/**
* 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.GraphId;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
@NodeEntity
public class Account2 {
private static final long serialVersionUID = 1L;
@GraphId
private Long graphId;
@Indexed(unique = true, failOnDuplicate = true)
private String accountNumber;
private String name;
public Account2() {
}
public Account2(String accountNumber, String name) {
this.accountNumber = accountNumber;
this.name = name;
}
public Long getGraphId() {
return graphId;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account2 person = (Account2) o;
if (graphId == null) return super.equals(o);
return graphId.equals(person.graphId);
}
@Override
public int hashCode() {
return graphId != null ? graphId.hashCode() : super.hashCode();
}
}

View File

@@ -0,0 +1,11 @@
package org.springframework.data.neo4j.repositories;
import org.springframework.data.neo4j.model.Account1;
import org.springframework.data.neo4j.repository.GraphRepository;
/**
* @author Nicki Watt
* @since 06-04-2014
*/
public interface Account1Repository extends GraphRepository<Account1> {
}

View File

@@ -0,0 +1,11 @@
package org.springframework.data.neo4j.repositories;
import org.springframework.data.neo4j.model.Account2;
import org.springframework.data.neo4j.repository.GraphRepository;
/**
* @author Nicki Watt
* @since 06-04-2014
*/
public interface Account2Repository extends GraphRepository<Account2> {
}

View File

@@ -20,6 +20,7 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.ConstraintViolationException;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.helpers.collection.IteratorUtil;
@@ -86,6 +87,10 @@ public class GraphRepositoryTests {
@Autowired
private BeingRepository beingRepository;
@Autowired
private Account1Repository account1Repository;
@Autowired
private Account2Repository account2Repository;
@Autowired
org.springframework.data.neo4j.repositories.GroupRepository groupRepository;
@Autowired
@@ -155,6 +160,45 @@ public class GraphRepositoryTests {
});
}
@Test
@Transactional
public void testSaveWithDefaultFailOnDuplicateSetToFalse() {
// Account1
// @Indexed(unique = true, failOnDuplicate = false)
// private String accountNumber;
Account1 acc1 = new Account1("111-222-333", "Mr George - Current Account 1");
Account1 acc2 = new Account1("111-222-333", "Mr George - Current Account 2");
Account1 savedAcc1 = account1Repository.save(acc1);
Account1 savedAcc2 = account1Repository.save(acc2);
assertEquals("expecting the saving of the same entity result in a merge of nodes", savedAcc1.getGraphId(), savedAcc2.getGraphId());
}
@Test(expected = ConstraintViolationException.class)
@Transactional
public void testSaveWithDefaultFailOnDuplicateSetToTrue() {
// Account2
// @Indexed(unique = true, failOnDuplicate = true)
// private String accountNumber;
Account2 acc1 = new Account2("111-222-333", "Mr George - Current Account 1");
Account2 acc2 = new Account2("111-222-333", "Mr George - Current Account 2");
Account2 savedAcc1 = account2Repository.save(acc1);
Account2 savedAcc2 = account2Repository.save(acc2);
}
@Test
@Transactional
public void testSaveWithDefaultFailOnDuplicateSetToTrueAllowsUpdates() {
// Account2
// @Indexed(unique = true, failOnDuplicate = true)
// private String accountNumber;
Account2 acc1 = new Account2("111-222-333", "Mr George - Current Account 1");
Account2 savedAcc1 = account2Repository.save(acc1);
acc1.setName("Mr George - Current Account 2");
account2Repository.save(savedAcc1);
// No exception expected!
}
@Test
@Transactional
public void findAll() {

View File

@@ -30,12 +30,10 @@ import org.neo4j.helpers.collection.MapUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.neo4j.conversion.Result;
import org.springframework.data.neo4j.mapping.ManagedEntity;
import org.springframework.data.neo4j.model.Friendship;
import org.springframework.data.neo4j.model.Group;
import org.springframework.data.neo4j.model.Named;
import org.springframework.data.neo4j.model.Person;
import org.springframework.data.neo4j.model.*;
import org.springframework.data.neo4j.repository.GraphRepository;
import org.springframework.data.neo4j.support.query.CypherQueryEngine;
import org.springframework.data.neo4j.template.Neo4jOperations;
@@ -265,6 +263,33 @@ public class EntityNeo4jTemplateTests extends EntityTestBase {
assertEquals(false, template.isRelationshipEntity(Object.class));
}
@Test @Transactional
public void testDefaultCreateUniqueNodeWithFailOnDuplicateSetToFalse() throws Exception {
final Account1 acc = new Account1("111-222-333", "Mr George - Current Account");
Node acc1Node = template.createUniqueNode(acc);
Node acc2Node = template.createUniqueNode(acc);
assertEquals("When failOnDuplicate is set to false, duplicate creations should be merged to same node", acc1Node.getId(), acc2Node.getId());
}
@Test(expected = ConstraintViolationException.class) @Transactional
public void testDefaultCreateUniqueNodeWithFailOnDuplicateSetToTrue() throws Exception {
final Account2 acc = new Account2("111-222-333", "Mr George - Current Account");
Node acc1Node = template.createUniqueNode(acc);
Node acc2Node = template.createUniqueNode(acc);
}
@Ignore("Not able to take failOnDuplicate into account")
@Test @Transactional
public void testCreateNodeAsIgnoresFailOnDuplicateValue() throws Exception {
Account1 acc1 = template.createNodeAs(Account1.class,map("accountNumber","111-222-333","name","Mr George - Current Account"));
Account1 acc2 = template.createNodeAs(Account1.class,map("accountNumber","111-222-333","name","Mr George - Current Account"));
// When failOnDuplicate is set to false, createNodeAs should not be
// throwing an exception, but it currently does as it does not take
// this indo into account?
}
@Test @Transactional
public void testSave() throws Exception {
final Person thomas = new Person("Thomas", 30);