DATAGRAPH-700 - Support mapping of domain entities in @QueryResults.

This commit is contained in:
Luanne Misquitta
2016-01-22 18:35:41 +05:30
parent a3b82cfbdf
commit 14a9fbc82c
6 changed files with 229 additions and 47 deletions

View File

@@ -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 <code>&#064;QueryResult</code>.
@@ -61,22 +57,20 @@ public class QueryResultGraphRepositoryQuery extends GraphRepositoryQuery {
return resultObjects.isEmpty() ? null : resultObjects.iterator().next();
}
private Collection<Object> mapToConcreteType(final Class<?> targetType, String cypherQuery, Map<String, Object> queryParams) {
private Collection<Object> mapToConcreteType(final Class<?> targetType, final String cypherQuery, final Map<String, Object> queryParams) {
final RowModelRequest qry = new DefaultRowModelRequest(cypherQuery, queryParams);
return this.session.doInTransaction(new GraphCallback<Collection<Object>>() {
@Override
public Collection<Object> apply(Request requestHandler, Transaction transaction, MetaData metaData) {
try (Response<RowModel> response = requestHandler.execute(qry)) {
Collection<Object> 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<Object> toReturn = new ArrayList<>();
SingleUseEntityMapper entityMapper = new SingleUseEntityMapper(metaData, new EntityFactory(metaData));
Iterable<Map<String,Object>> results = session.query(cypherQuery, queryParams);
for (Map<String,Object> result : results) {
toReturn.add(entityMapper.map(targetType, result));
}
return toReturn;
}
});
}

View File

@@ -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<User> friends;
private List<Rating> ratings;
private TempMovie[] movies;
private float avgRating;
public User getUser() {
return user;
}
public Set<User> getFriends() {
return friends;
}
public List<Rating> getRatings() {
return ratings;
}
public float getAvgRating() {
return avgRating;
}
public TempMovie[] getMovies() {
return movies;
}
}

View File

@@ -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();
}

View File

@@ -69,12 +69,27 @@ public interface UserRepository extends GraphRepository<User> {
@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<RichUserQueryResult> 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<EntityWrappingQueryResult> findAllUserRatings();
@Query("MATCH (user:User) RETURN ID(user)")
List<Long> getUserNodeIds();

View File

@@ -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<String> 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<String> 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<String> 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<EntityWrappingQueryResult> 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<String> 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());
}
}

View File

@@ -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<Map<String, Object>> loadNodes() {
return new Neo4jTemplate(session).query("MATCH n RETURN n", new HashMap<String, Object>());
return new Neo4jTemplate(session).query("MATCH n RETURN n.name", new HashMap<String, Object>());
}
public void purge() {