From 14a9fbc82ca81147ff6da38b184c8ec5cb7b9d0f Mon Sep 17 00:00:00 2001 From: Luanne Misquitta Date: Fri, 22 Jan 2016 18:35:41 +0530 Subject: [PATCH] DATAGRAPH-700 - Support mapping of domain entities in @QueryResults. --- .../QueryResultGraphRepositoryQuery.java | 38 ++-- .../EntityWrappingQueryResult.java | 24 +++ .../queryresult/UserQueryResultInterface.java | 3 + .../examples/movies/repo/UserRepository.java | 15 ++ .../neo4j/queries/QueryIntegrationTest.java | 192 +++++++++++++++--- .../transactions/service/BusinessService.java | 4 +- 6 files changed, 229 insertions(+), 47 deletions(-) diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryResultGraphRepositoryQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryResultGraphRepositoryQuery.java index 98f365aa8..57382c086 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryResultGraphRepositoryQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryResultGraphRepositoryQuery.java @@ -14,23 +14,19 @@ package org.springframework.data.neo4j.repository.query; -import org.neo4j.ogm.MetaData; -import org.neo4j.ogm.annotations.EntityFactory; -import org.neo4j.ogm.context.SingleUseEntityMapper; -import org.neo4j.ogm.cypher.query.DefaultRowModelRequest; -import org.neo4j.ogm.model.RowModel; -import org.neo4j.ogm.request.Request; -import org.neo4j.ogm.request.RowModelRequest; -import org.neo4j.ogm.response.Response; -import org.neo4j.ogm.session.GraphCallback; -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.transaction.Transaction; +import static java.lang.reflect.Proxy.newProxyInstance; import java.util.ArrayList; import java.util.Collection; import java.util.Map; -import static java.lang.reflect.Proxy.newProxyInstance; +import org.neo4j.ogm.MetaData; +import org.neo4j.ogm.annotations.EntityFactory; +import org.neo4j.ogm.context.SingleUseEntityMapper; +import org.neo4j.ogm.request.Request; +import org.neo4j.ogm.session.GraphCallback; +import org.neo4j.ogm.session.Session; +import org.neo4j.ogm.transaction.Transaction; /** * Specialisation of {@link GraphRepositoryQuery} that handles mapping to objects annotated with @QueryResult. @@ -61,22 +57,20 @@ public class QueryResultGraphRepositoryQuery extends GraphRepositoryQuery { return resultObjects.isEmpty() ? null : resultObjects.iterator().next(); } - private Collection mapToConcreteType(final Class targetType, String cypherQuery, Map queryParams) { + private Collection mapToConcreteType(final Class targetType, final String cypherQuery, final Map queryParams) { + - final RowModelRequest qry = new DefaultRowModelRequest(cypherQuery, queryParams); return this.session.doInTransaction(new GraphCallback>() { @Override public Collection apply(Request requestHandler, Transaction transaction, MetaData metaData) { - try (Response response = requestHandler.execute(qry)) { - Collection toReturn = new ArrayList<>(); - - SingleUseEntityMapper entityMapper = new SingleUseEntityMapper(metaData, new EntityFactory(metaData)); - for (RowModel rowModel = response.next(); rowModel != null; rowModel = response.next()) { - toReturn.add(entityMapper.map(targetType, response.columns(), rowModel)); - } - return toReturn; + Collection toReturn = new ArrayList<>(); + SingleUseEntityMapper entityMapper = new SingleUseEntityMapper(metaData, new EntityFactory(metaData)); + Iterable> results = session.query(cypherQuery, queryParams); + for (Map result : results) { + toReturn.add(entityMapper.map(targetType, result)); } + return toReturn; } }); } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/queryresult/EntityWrappingQueryResult.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/queryresult/EntityWrappingQueryResult.java index 723724cb2..9f2eb83da 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/queryresult/EntityWrappingQueryResult.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/queryresult/EntityWrappingQueryResult.java @@ -13,7 +13,12 @@ package org.springframework.data.neo4j.examples.movies.domain.queryresult; +import java.util.List; +import java.util.Set; + import org.springframework.data.neo4j.annotation.QueryResult; +import org.springframework.data.neo4j.examples.movies.domain.Rating; +import org.springframework.data.neo4j.examples.movies.domain.TempMovie; import org.springframework.data.neo4j.examples.movies.domain.User; /** @@ -23,9 +28,28 @@ import org.springframework.data.neo4j.examples.movies.domain.User; public class EntityWrappingQueryResult { private User user; + private Set friends; + private List ratings; + private TempMovie[] movies; + private float avgRating; public User getUser() { return user; } + public Set getFriends() { + return friends; + } + + public List getRatings() { + return ratings; + } + + public float getAvgRating() { + return avgRating; + } + + public TempMovie[] getMovies() { + return movies; + } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/queryresult/UserQueryResultInterface.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/queryresult/UserQueryResultInterface.java index 091a5303f..c8a763872 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/queryresult/UserQueryResultInterface.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/domain/queryresult/UserQueryResultInterface.java @@ -15,6 +15,7 @@ package org.springframework.data.neo4j.examples.movies.domain.queryresult; import org.neo4j.ogm.annotation.Property; import org.springframework.data.neo4j.annotation.QueryResult; +import org.springframework.data.neo4j.examples.movies.domain.User; import org.springframework.data.neo4j.examples.movies.repo.UserRepository; /** @@ -31,4 +32,6 @@ public interface UserQueryResultInterface { int getAgeOfUser(); + User getUser(); + } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/repo/UserRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/repo/UserRepository.java index 5fd86d1bf..3d16474c3 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/repo/UserRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/examples/movies/repo/UserRepository.java @@ -69,12 +69,27 @@ public interface UserRepository extends GraphRepository { @Query("MATCH (user:User) WHERE user.name={0} RETURN user.name, user.age AS ageOfUser") UserQueryResultInterface findIndividualUserAsProxiedObject(String name); + @Query("MATCH (user:User) WHERE user.name={0} RETURN user as user, user.age AS ageOfUser") + UserQueryResultInterface findWrappedUserAsProxiedObject(String name); + @Query("MATCH (user:User) WHERE user.gender={0} RETURN user.name AS UserName, user.gender AS UserGender, user.account as UserAccount, user.deposits as UserDeposits") Iterable findUsersByGender(Gender gender); @Query("MATCH (user:User) WHERE user.name={0} RETURN user") EntityWrappingQueryResult findWrappedUserByName(String userName); + @Query("MATCH (user:User)-[:FRIEND_OF]->(f) WHERE user.name={0} RETURN user, collect(f) as friends") + EntityWrappingQueryResult findWrappedUserAndFriendsDepth0(String userName); + + @Query("MATCH (user:User)-[r:FRIEND_OF]->(f) WHERE user.name={0} RETURN user, collect(r) as rels, collect(f) as friends") + EntityWrappingQueryResult findWrappedUserAndFriendsDepth1(String userName); + + @Query("MATCH (user:User)-[r:RATED]->(m) WHERE user.name={0} RETURN user, collect(r) as ratings, collect(m) as movies, avg(r.stars) as avgRating") + EntityWrappingQueryResult findWrappedUserAndRatingsByName(String userName); + + @Query("MATCH (user:User)-[r:RATED]->(m) RETURN user, collect(r) as ratings, collect(m) as movies, avg(r.stars) as avgRating order by user.name desc") + List findAllUserRatings(); + @Query("MATCH (user:User) RETURN ID(user)") List getUserNodeIds(); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/queries/QueryIntegrationTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/queries/QueryIntegrationTest.java index 3bbb231bb..a64b06778 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/queries/QueryIntegrationTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/queries/QueryIntegrationTest.java @@ -13,9 +13,24 @@ package org.springframework.data.neo4j.queries; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.neo4j.graphdb.GraphDatabaseService; @@ -23,8 +38,14 @@ import org.neo4j.ogm.exception.MappingException; import org.neo4j.ogm.testutil.MultiDriverTestClass; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.neo4j.examples.movies.context.MoviesContext; +import org.springframework.data.neo4j.examples.movies.domain.Rating; +import org.springframework.data.neo4j.examples.movies.domain.TempMovie; import org.springframework.data.neo4j.examples.movies.domain.User; -import org.springframework.data.neo4j.examples.movies.domain.queryresult.*; +import org.springframework.data.neo4j.examples.movies.domain.queryresult.EntityWrappingQueryResult; +import org.springframework.data.neo4j.examples.movies.domain.queryresult.Gender; +import org.springframework.data.neo4j.examples.movies.domain.queryresult.RichUserQueryResult; +import org.springframework.data.neo4j.examples.movies.domain.queryresult.UserQueryResult; +import org.springframework.data.neo4j.examples.movies.domain.queryresult.UserQueryResultInterface; import org.springframework.data.neo4j.examples.movies.repo.CinemaRepository; import org.springframework.data.neo4j.examples.movies.repo.UnmanagedUserPojo; import org.springframework.data.neo4j.examples.movies.repo.UserRepository; @@ -32,12 +53,6 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.*; - -import static org.junit.Assert.*; - /** * @author Vince Bickers * @author Luanne Misquitta @@ -209,21 +224,6 @@ public class QueryIntegrationTest extends MultiDriverTestClass { assertFalse(userIterator.hasNext()); } - /** - * I'm not sure whether we should actually support this because you could just return an entity! - */ - @Ignore - @Test - public void shouldMapNodeEntitiesIntoQueryResultObjects() { - executeUpdate("CREATE (:User {name:'Abraham'}), (:User {name:'Barry'}), (:User {name:'Colin'})"); - - EntityWrappingQueryResult wrappedUser = userRepository.findWrappedUserByName("Barry"); - assertNotNull("The loaded wrapper object shouldn't be null", wrappedUser); - assertNotNull("The enclosed user shouldn't be null", wrappedUser.getUser()); - assertEquals("Barry", wrappedUser.getUser().getName()); - } - - /** * @see DATAGRAPH-694 */ @@ -298,4 +298,150 @@ public class QueryIntegrationTest extends MultiDriverTestClass { } } + /** + * @see DATAGRAPH-700 + */ + @Test + public void shouldMapNodeEntitiesIntoQueryResultObjects() { + executeUpdate("CREATE (:User {name:'Abraham'}), (:User {name:'Barry'}), (:User {name:'Colin'})"); + + EntityWrappingQueryResult wrappedUser = userRepository.findWrappedUserByName("Barry"); + assertNotNull("The loaded wrapper object shouldn't be null", wrappedUser); + assertNotNull("The enclosed user shouldn't be null", wrappedUser.getUser()); + assertEquals("Barry", wrappedUser.getUser().getName()); + } + + /** + * @see DATAGRAPH-700 + */ + @Test + public void shouldMapNodeCollectionsIntoQueryResultObjects() { + executeUpdate("CREATE (d:User {name:'Daniela'}), (e:User {name:'Ethan'}), (f:User {name:'Finn'}), (d)-[:FRIEND_OF]->(e), (d)-[:FRIEND_OF]->(f)"); + + EntityWrappingQueryResult result = userRepository.findWrappedUserAndFriendsDepth1("Daniela"); + assertNotNull("The result shouldn't be null", result); + assertNotNull("The enclosed user shouldn't be null", result.getUser()); + assertEquals("Daniela", result.getUser().getName()); + assertEquals(2, result.getFriends().size()); + List friends = new ArrayList<>(); + for (User u : result.getFriends()) { + friends.add(u.getName()); + } + assertTrue(friends.contains("Ethan")); + assertTrue(friends.contains("Finn")); + assertEquals(2, result.getUser().getFriends().size()); //we expect friends to be mapped since the relationships were returned + } + + /** + * @see DATAGRAPH-700 + */ + @Test + public void shouldMapRECollectionsIntoQueryResultObjects() { + executeUpdate("CREATE (g:User {name:'Gary'}), (sw:Movie {name: 'Star Wars: The Force Awakens'}), (hob:Movie {name:'The Hobbit: An Unexpected Journey'}), (g)-[:RATED {stars : 5}]->(sw), (g)-[:RATED {stars: 4}]->(hob) "); + + EntityWrappingQueryResult result = userRepository.findWrappedUserAndRatingsByName("Gary"); + assertNotNull("The loaded wrapper object shouldn't be null", result); + assertNotNull("The enclosed user shouldn't be null", result.getUser()); + assertEquals("Gary", result.getUser().getName()); + assertEquals(2, result.getRatings().size()); + for (Rating rating : result.getRatings()) { + if (rating.getStars() == 4) { + assertEquals("The Hobbit: An Unexpected Journey", rating.getMovie().getName()); + } + else { + assertEquals("Star Wars: The Force Awakens", rating.getMovie().getName()); + } + } + + assertEquals(4.5f, result.getAvgRating(),0); + assertEquals(2, result.getMovies().length); + List titles = new ArrayList<>(); + for (TempMovie movie : result.getMovies()) { + titles.add(movie.getName()); + } + assertTrue(titles.contains("The Hobbit: An Unexpected Journey")); + assertTrue(titles.contains("Star Wars: The Force Awakens")); + } + + /** + * @see DATAGRAPH-700 + */ + @Test + public void shouldMapRelationshipCollectionsWithDepth0IntoQueryResultObjects() { + executeUpdate("CREATE (i:User {name:'Ingrid'}), (j:User {name:'Jake'}), (k:User {name:'Kate'}), (i)-[:FRIEND_OF]->(j), (i)-[:FRIEND_OF]->(k)"); + + EntityWrappingQueryResult result = userRepository.findWrappedUserAndFriendsDepth0("Ingrid"); + assertNotNull("The result shouldn't be null", result); + assertNotNull("The enclosed user shouldn't be null", result.getUser()); + assertEquals("Ingrid", result.getUser().getName()); + assertEquals(2, result.getFriends().size()); + List friends = new ArrayList<>(); + for (User u : result.getFriends()) { + friends.add(u.getName()); + } + assertTrue(friends.contains("Kate")); + assertTrue(friends.contains("Jake")); + assertEquals(0, result.getUser().getFriends().size()); //we do not expect friends to be mapped since the relationships were not returned + + } + + /** + * @see DATAGRAPH-700 + */ + @Test + public void shouldReturnMultipleQueryResultObjects() { + executeUpdate("CREATE (g:User {name:'Gary'}), (h:User {name:'Harry'}), (sw:Movie {name: 'Star Wars: The Force Awakens'}), (hob:Movie {name:'The Hobbit: An Unexpected Journey'}), (g)-[:RATED {stars : 5}]->(sw), (g)-[:RATED {stars: 4}]->(hob), (h)-[:RATED {stars: 3}]->(hob) "); + + List results = userRepository.findAllUserRatings(); + assertEquals(2, results.size()); + EntityWrappingQueryResult result = results.get(0); + + assertNotNull("The loaded wrapper object shouldn't be null", result); + assertNotNull("The enclosed user shouldn't be null", result.getUser()); + assertEquals("Harry", result.getUser().getName()); + assertEquals(1, result.getRatings().size()); + Rating rating = result.getRatings().get(0); + assertEquals("The Hobbit: An Unexpected Journey", rating.getMovie().getName()); + assertEquals(3, rating.getStars()); + assertEquals(3f, result.getAvgRating(),0); + assertEquals(1, result.getMovies().length); + assertEquals("The Hobbit: An Unexpected Journey", result.getMovies()[0].getName()); + + result = results.get(1); + assertNotNull("The loaded wrapper object shouldn't be null", result); + assertNotNull("The enclosed user shouldn't be null", result.getUser()); + assertEquals("Gary", result.getUser().getName()); + for (Rating r : result.getRatings()) { + if (r.getStars() == 4) { + assertEquals("The Hobbit: An Unexpected Journey", r.getMovie().getName()); + } + else { + assertEquals("Star Wars: The Force Awakens", r.getMovie().getName()); + } + } + + assertEquals(4.5f, result.getAvgRating(),0); + assertEquals(2, result.getMovies().length); + List titles = new ArrayList<>(); + for (TempMovie movie : result.getMovies()) { + titles.add(movie.getName()); + } + assertTrue(titles.contains("The Hobbit: An Unexpected Journey")); + assertTrue(titles.contains("Star Wars: The Force Awakens")); + } + + /** + * @see DATAGRAPH-700 + */ + @Test + public void shouldMapEntitiesToProxiedQueryResultInterface() { + executeUpdate("CREATE (:User {name:'Morne', age:30}), (:User {name:'Abraham', age:31}), (:User {name:'Virat', age:27})"); + + UserQueryResultInterface result = userRepository.findWrappedUserAsProxiedObject("Abraham"); + assertNotNull("The query result shouldn't be null", result); + assertNotNull("The mapped user shouldn't be null", result.getUser()); + assertEquals("The wrong user was returned", "Abraham", result.getUser().getName()); + assertEquals("The wrong user was returned", 31, result.getAgeOfUser()); + } + } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/transactions/service/BusinessService.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/transactions/service/BusinessService.java index fcabe915e..d3d44813f 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/transactions/service/BusinessService.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/transactions/service/BusinessService.java @@ -46,11 +46,11 @@ public class BusinessService { } private void insertNode() { - new Neo4jTemplate(session).execute("CREATE node"); + new Neo4jTemplate(session).execute("CREATE (node {name: 'n'})"); } public Iterable> loadNodes() { - return new Neo4jTemplate(session).query("MATCH n RETURN n", new HashMap()); + return new Neo4jTemplate(session).query("MATCH n RETURN n.name", new HashMap()); } public void purge() {