Merge pull request #192 from nickithewatt/DATAGRAPH-445
DATAGRAPH-445 Implement create_or_fail for GraphRepository
This commit is contained in:
@@ -90,12 +90,12 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j</artifactId>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j</artifactId>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -17,21 +17,22 @@
|
||||
package org.springframework.data.neo4j.aspects.support;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
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.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 +41,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 +125,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 = DataIntegrityViolationException.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);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,13 +20,17 @@ 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;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
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 +49,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 +275,45 @@ 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 = DataIntegrityViolationException.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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
@@ -48,25 +48,25 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j-aspects</artifactId>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j-tx</artifactId>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j</artifactId>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j</artifactId>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j</artifactId>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.neo4j</groupId>
|
||||
@@ -223,14 +223,14 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j-aspects</artifactId>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j</artifactId>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
@@ -238,7 +238,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j-aspects</artifactId>
|
||||
<version>3.1.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
|
||||
@@ -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,21 @@ 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
|
||||
/*
|
||||
Unfortunately the REST scenario does not provide us with enough info to work out that this
|
||||
was a constaint violation (other than parsing the message itself which is not great) as it
|
||||
merely throws an IllegalStateException when no content is found for a response when adding
|
||||
labels. The org.neo4j.rest.graphdb.ExecutingRestAPI.addLabels(RestNode node, String...labels)
|
||||
method is the current offender in this case. The REST API itself would probably need to change for
|
||||
us to be able to deal with this appropriately in SDN
|
||||
*/
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSaveWhenFailOnDuplicateSetToTrue() {
|
||||
super.testSaveWhenFailOnDuplicateSetToTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.data.neo4j.support;
|
||||
|
||||
import org.neo4j.graphdb.ConstraintViolationException;
|
||||
import org.neo4j.graphdb.NotFoundException;
|
||||
import org.neo4j.graphdb.NotInTransactionException;
|
||||
import org.neo4j.graphdb.TransactionFailureException;
|
||||
@@ -45,6 +46,8 @@ public class Neo4jExceptionTranslator implements PersistenceExceptionTranslator
|
||||
throw (InvalidEntityTypeException)iae.getCause();
|
||||
}
|
||||
throw new InvalidDataAccessApiUsageException(iae.getMessage(),iae);
|
||||
} catch(ConstraintViolationException cve) {
|
||||
throw new DataIntegrityViolationException(cve.getMessage(),cve);
|
||||
} catch(DataAccessException dae) {
|
||||
throw dae;
|
||||
} catch(NotInTransactionException nit) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.springframework.data.neo4j.support.node;
|
||||
|
||||
import org.neo4j.graphdb.ConstraintViolationException;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.NotInTransactionException;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
|
||||
import org.springframework.data.neo4j.fieldaccess.DefaultEntityState;
|
||||
@@ -61,6 +64,8 @@ public class NodeEntityState extends DefaultEntityState<Node> {
|
||||
template.postEntityCreation(node, type);
|
||||
} catch (NotInTransactionException e) {
|
||||
throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e);
|
||||
} catch (ConstraintViolationException e) {
|
||||
throw new DataIntegrityViolationException(e.getMessage(),e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.neo4j.helpers.collection.IteratorUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Slice;
|
||||
@@ -63,7 +64,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.neo4j.helpers.collection.IteratorUtil.addToCollection;
|
||||
import static org.neo4j.helpers.collection.IteratorUtil.asCollection;
|
||||
import static org.neo4j.helpers.collection.MapUtil.map;
|
||||
@@ -86,6 +86,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 +159,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 = DataIntegrityViolationException.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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user