From bbe042c53b0f60c936c43a764f398498e8a6a4da Mon Sep 17 00:00:00 2001 From: Luanne Misquitta Date: Fri, 13 Nov 2015 17:35:32 +0530 Subject: [PATCH] DATAGRAPH-685 - LoadByProperty depth support in Neo4jOperations. --- .../data/neo4j/template/Neo4jOperations.java | 58 +++++++++ .../data/neo4j/template/Neo4jTemplate.java | 21 +++- .../neo4j/examples/movies/domain/User.java | 4 + .../neo4j/template/Neo4jTemplateTest.java | 113 +++++++++++++++++- .../programming-model/repositories.adoc | 7 +- 5 files changed, 198 insertions(+), 5 deletions(-) diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java index 418d82bb6..e85f046c3 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java @@ -95,6 +95,23 @@ public interface Neo4jOperations { */ T loadByProperty(Class type, String propertyName, Object propertyValue); + /** + * Retrieves the entity of the specified type that contains a property matching the given name with the given value. + * This method assumes that the requested property/value combination will be unique for all entities of this type in + * the database and will throw an exception unless exactly one result is found. If several entities are expected to + * be returned then use {@link #loadAllByProperty(Class, String, Object)} instead. + * + * @param type The type of entity to load + * @param propertyName The name of the property on the entity against which to match the given value + * @param propertyValue The value of the named property against which to match entities + * @param depth The maximum number of relationships away from each loaded object to follow when loading related entities. + * A value of 0 just loads the object's properties and no related entities. A value of -1 implies no depth limit. + * @return The instance of T corresponding to the entity that matches the given property, never null + * @throws NotFoundException if there are no matching entities + * @throws IllegalStateException if there's more than one matching entity + */ + T loadByProperty(Class type, String propertyName, Object propertyValue, int depth); + /** * Retrieves all the entities of the specified type that contain a property matching the given name with the given value. * @@ -106,6 +123,19 @@ public interface Neo4jOperations { */ Collection loadAllByProperty(Class type, String propertyName, Object propertyValue); + /** + * Retrieves all the entities of the specified type that contain a property matching the given name with the given value. + * + * @param type The type of entity to load + * @param propertyName The name of the property on the entity against which to match the given value + * @param propertyValue The value of the named property against which to match entities + * @param depth The maximum number of relationships away from each loaded object to follow when loading related entities. + * A value of 0 just loads the object's properties and no related entities. A value of -1 implies no depth limit. + * @return A {@link Collection} containing all the entities that match the given property or an empty {@link Collection} if + * there aren't any matches, never null + */ + Collection loadAllByProperty(Class type, String propertyName, Object propertyValue, int depth); + /** * Retrieves the entity of the specified type that contains properties matching the ones supplied with given name and value. * This method assumes that the requested property/value combinations will be unique for all entities of this type in @@ -120,6 +150,22 @@ public interface Neo4jOperations { */ T loadByProperties(Class type, Filters parameters); + /** + * Retrieves the entity of the specified type that contains properties matching the ones supplied with given name and value. + * This method assumes that the requested property/value combinations will be unique for all entities of this type in + * the database and will throw an exception unless exactly one result is found. If several entities are expected to + * be returned then use {@link #loadAllByProperty(Class, String, Object)} instead. + * + * @param type The type of entity to load + * @param parameters The parameters to filter by + * @param depth The maximum number of relationships away from each loaded object to follow when loading related entities. + * A value of 0 just loads the object's properties and no related entities. A value of -1 implies no depth limit. + * @return The instance of T corresponding to the entity that matches the given properties, never null + * @throws NotFoundException if there are no matching entities + * @throws IllegalStateException if there's more than one matching entity + */ + T loadByProperties(Class type, Filters parameters, int depth); + /** * Retrieves all the entities of the specified type that contain a properties matching the ones supplied with given name and value. * @@ -130,6 +176,18 @@ public interface Neo4jOperations { */ Collection loadAllByProperties(Class type, Filters parameters); + /** + * Retrieves all the entities of the specified type that contain a properties matching the ones supplied with given name and value. + * + * @param type The type of entity to load + * @param parameters The parameters to filter by + * @param depth The maximum number of relationships away from each loaded object to follow when loading related entities. + * A value of 0 just loads the object's properties and no related entities. A value of -1 implies no depth limit. + * @return A {@link Collection} containing all the entities that match the given properties or an empty {@link Collection} if + * there aren't any matches, never null + */ + Collection loadAllByProperties(Class type, Filters parameters, int depth); + /** * Saves the specified entity in the graph database. If the entity is currently transient then the persistent version of * the entity will be returned, containing its new graph ID. diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jTemplate.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jTemplate.java index 5caaedddc..ea57310c2 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jTemplate.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jTemplate.java @@ -138,8 +138,13 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationEventPublisher @Override public T loadByProperty(Class type, String propertyName, Object propertyValue) { + return loadByProperty(type, propertyName, propertyValue, 1); + } + + @Override + public T loadByProperty(Class type, String propertyName, Object propertyValue, int depth) { try { - return getSingle(loadAllByProperty(type, propertyName, propertyValue)); + return getSingle(loadAllByProperty(type, propertyName, propertyValue, depth)); } catch (Exception e) { throw Neo4jOgmExceptionTranslator.translateExceptionIfPossible(e); } @@ -164,8 +169,13 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationEventPublisher @Override public T loadByProperties(Class type, Filters parameters) { + return loadByProperties(type, parameters, 1); + } + + @Override + public T loadByProperties(Class type, Filters parameters, int depth) { try { - return getSingle(loadAllByProperties(type, parameters)); + return getSingle(loadAllByProperties(type, parameters, depth)); } catch (Exception e) { throw Neo4jOgmExceptionTranslator.translateExceptionIfPossible(e); } @@ -173,8 +183,13 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationEventPublisher @Override public Collection loadAllByProperties(Class type, Filters parameters) { + return loadAllByProperties(type, parameters, 1); + } + + @Override + public Collection loadAllByProperties(Class type, Filters parameters, int depth) { try { - return session.loadAll(type, parameters); + return session.loadAll(type, parameters, depth); } catch (Exception e) { throw Neo4jOgmExceptionTranslator.translateExceptionIfPossible(e); } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/User.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/User.java index 6e4f7c0a7..dcefa526b 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/User.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/User.java @@ -72,4 +72,8 @@ public class User extends Person{ public Set getRatings() { return ratings; } + + public void setMiddleName(String middleName) { + this.middleName = middleName; + } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateTest.java index b248c412f..abfac9103 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateTest.java @@ -27,9 +27,11 @@ import org.junit.Test; import org.neo4j.cypher.javacompat.ExecutionEngine; import org.neo4j.graphdb.*; import org.neo4j.ogm.annotation.NodeEntity; +import org.neo4j.ogm.cypher.BooleanOperator; import org.neo4j.ogm.cypher.ComparisonOperator; import org.neo4j.ogm.cypher.Filter; import org.neo4j.ogm.cypher.Filters; +import org.neo4j.ogm.session.Session; import org.neo4j.ogm.session.SessionFactory; import org.neo4j.ogm.session.Utils; import org.neo4j.ogm.session.result.QueryStatistics; @@ -48,11 +50,13 @@ public class Neo4jTemplateTest { public static Neo4jIntegrationTestRule neo4jRule = new Neo4jIntegrationTestRule(); private Neo4jOperations template; + private Session session; @Before public void setUpOgmSession() { SessionFactory sessionFactory = new SessionFactory("org.springframework.data.neo4j.examples.movies.domain"); - this.template = new Neo4jTemplate(sessionFactory.openSession(neo4jRule.url())); + session = sessionFactory.openSession(neo4jRule.url()); + this.template = new Neo4jTemplate(session); addArbitraryDataToDatabase(); } @@ -164,6 +168,113 @@ public class Neo4jTemplateTest { assertEquals("Horror", loadedGenre.getName()); } + /** + * @see DATAGRAPH-685 + */ + @Test + public void shouldRetrieveEntitiesByMatchingPropertyAndDepth() { + User user = new User("Harmanpreet Singh"); + TempMovie bollywood = new TempMovie("Desi Boyz"); + TempMovie hollywood = new TempMovie("Mission Impossible"); + template.save(user.rate(bollywood, 1, "Bakwaas")); + template.save(user.rate(hollywood, 4, "Pretty good")); + + session.clear(); + + User u = template.loadByProperty(User.class, "name", "Harmanpreet Singh",0); + assertEquals(0,u.getRatings().size()); + + u = template.loadByProperty(User.class, "name", "Harmanpreet Singh",2); + assertEquals(2,u.getRatings().size()); + assertNotNull(u.getRatings().iterator().next().getMovie().getRatings()); + } + + /** + * @see DATAGRAPH-685 + */ + @Test + public void shouldRetrieveAllEntitiesByMatchingPropertyAndDepth() { + User user = new User("Harmanpreet Singh"); + TempMovie bollywood = new TempMovie("Desi Boyz"); + TempMovie hollywood = new TempMovie("Desi Boyz"); + template.save(user.rate(bollywood, 1, "Bakwaas")); + template.save(user.rate(hollywood, 4, "Pretty good")); + + session.clear(); + + Collection m = template.loadAllByProperty(TempMovie.class, "name", "Desi Boyz",0); + assertEquals(2,m.size()); + assertEquals(0, m.iterator().next().getRatings().size()); + + m = template.loadAllByProperty(TempMovie.class, "name", "Desi Boyz",1); + assertEquals(2,m.size()); + assertEquals(1, m.iterator().next().getRatings().size()); + } + + + /** + * @see DATAGRAPH-685 + */ + @Test + public void shouldRetrieveEntitiesByMatchingPropertiesAndDepth() { + User user = new User("Harmanpreet Singh"); + user.setMiddleName("A"); + User user2 = new User("Harmanpreet Singh"); + user2.setMiddleName("B"); + TempMovie bollywood = new TempMovie("Desi Boyz"); + TempMovie hollywood = new TempMovie("Mission Impossible"); + template.save(user.rate(bollywood, 1, "Bakwaas")); + template.save(user.rate(hollywood, 4, "Pretty good")); + template.save(user2); + + session.clear(); + + Filter nameFilter = new Filter("name","Harmanpreet Singh"); + Filter middleNameFilter = new Filter("middleName","A"); + middleNameFilter.setBooleanOperator(BooleanOperator.AND); + Filters filters = new Filters(); + filters.add(nameFilter, middleNameFilter); + + User u = template.loadByProperties(User.class,filters,0); + assertEquals(0,u.getRatings().size()); + + u = template.loadByProperties(User.class, filters,2); + assertEquals(2,u.getRatings().size()); + assertNotNull(u.getRatings().iterator().next().getMovie().getRatings()); + } + + /** + * @see DATAGRAPH-685 + */ + @Test + public void shouldRetrieveAllEntitiesByMatchingPropertiesAndDepth() { + User user = new User("Harmanpreet Singh"); + user.setMiddleName("A"); + User user2 = new User("Harmanpreet Singh"); + user2.setMiddleName("A"); + TempMovie bollywood = new TempMovie("Desi Boyz"); + TempMovie hollywood = new TempMovie("Mission Impossible"); + template.save(user.rate(bollywood, 1, "Bakwaas")); + template.save(user.rate(hollywood, 4, "Pretty good")); + template.save(user2); + + session.clear(); + + Filter nameFilter = new Filter("name","Harmanpreet Singh"); + Filter middleNameFilter = new Filter("middleName","A"); + middleNameFilter.setBooleanOperator(BooleanOperator.AND); + Filters filters = new Filters(); + filters.add(nameFilter, middleNameFilter); + + Collection u = template.loadAllByProperties(User.class,filters,0); + assertEquals(2,u.size()); + assertEquals(0,u.iterator().next().getRatings().size()); + + u = template.loadAllByProperties(User.class, filters,2); + assertEquals(2,u.size()); + assertNotNull(u.iterator().next().getRatings().iterator().next().getMovie().getRatings()); + } + @Test public void shouldExecuteArbitraryUpdateQuery() { assertTrue("There shouldn't be any genres in the database", this.template.loadAll(Genre.class).isEmpty()); diff --git a/src/main/asciidoc/reference/programming-model/repositories.adoc b/src/main/asciidoc/reference/programming-model/repositories.adoc index 1c2b9e91b..a96c379a2 100644 --- a/src/main/asciidoc/reference/programming-model/repositories.adoc +++ b/src/main/asciidoc/reference/programming-model/repositories.adoc @@ -59,6 +59,11 @@ The named or indexed parameter `{param}` will be substituted by the actual metho Node and Relationship-Entities are handled directly and converted into their respective ids, Iterables thereof as well. All other parameters types are provided directly (i.e. Strings, Longs, etc). +[NOTE] +==== +In the current version, custom queries do not support paging, sorting or a custom depth. +==== + === Query results Typical results for queries are `Iterable`, `Iterable>` or simply `Type`. @@ -140,7 +145,7 @@ public interface PersonRepository extends GraphRepository { [NOTE] ==== -In the current version, derived finders and custom queries do not support paging, sorting or a custom depth. +In the current version, derived finders do not support paging, sorting or a custom depth. ====