diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java index c7e4f4fe0..3c6c88406 100644 --- a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java @@ -26,10 +26,10 @@ public class Movie { String description; @RelatedTo(type="DIRECTED", direction = INCOMING) - Person director; + Director director; @RelatedTo(type = "ACTS_IN", direction = INCOMING) - Set actors; + Set actors; @RelatedToVia(type = "ACTS_IN", direction = INCOMING) Iterable roles; @@ -57,7 +57,7 @@ public class Movie { this.title = title; } - public Collection getActors() { + public Collection getActors() { return actors; } @@ -106,7 +106,7 @@ public class Movie { return allRatings == null ? Collections.emptyList() : IteratorUtil.asCollection(allRatings); } - public Person getDirector() { + public Director getDirector() { return director; } diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Role.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Role.java index d58ba10d1..fdbc52690 100644 --- a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Role.java +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Role.java @@ -13,7 +13,7 @@ import org.springframework.data.neo4j.annotation.StartNode; public class Role { @GraphId Long id; @EndNode Movie movie; - @StartNode Person actor; + @StartNode Actor actor; String name; @@ -38,7 +38,7 @@ public class Role { return movie; } - public Person getActor() { + public Actor getActor() { return actor; } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java index b8ccf6ad2..0e71c73af 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java @@ -141,7 +141,7 @@ public abstract class Neo4jConfiguration { @Bean public TypeRepresentationStrategyFactory typeRepresentationStrategyFactory() throws Exception { - return new TypeRepresentationStrategyFactory(graphDatabase()); + return new TypeRepresentationStrategyFactory(graphDatabase(), indexProvider()); } @Bean diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/ConvertingNodePropertyFieldAccessorFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/ConvertingNodePropertyFieldAccessorFactory.java index 36366414a..248c77892 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/ConvertingNodePropertyFieldAccessorFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/ConvertingNodePropertyFieldAccessorFactory.java @@ -62,15 +62,36 @@ public class ConvertingNodePropertyFieldAccessorFactory implements FieldAccessor propertyConverter = new PropertyConverter(template.getConversionService(),property); } + private boolean doConvert(final Object value) { + // Do convert by default + boolean doConvert = true; + // If the value if of type Object and a neo4j supported type, do not convert it + if (property.getType().equals(Object.class) && property.isNeo4jPropertyValue(value)) { + doConvert = false; + } + + return doConvert; + } + @Override public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) { - super.setValue(entity, propertyConverter.serializePropertyValue(newVal,targetType), mappingPolicy); + Object value = newVal; + // Convert the value if it's not of a neo4j supported type + if (doConvert(value)) { + value = propertyConverter.serializePropertyValue(value, targetType); + } + super.setValue(entity, value, mappingPolicy); return newVal; } @Override public Object doGetValue(final Object entity) { - return propertyConverter.deserializePropertyValue(super.doGetValue(entity)); + Object ret = super.doGetValue(entity); + if (doConvert(ret)) { + ret = propertyConverter.deserializePropertyValue(ret); + } + + return ret; } @Override diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentProperty.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentProperty.java index 9018540bc..130977364 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentProperty.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentProperty.java @@ -54,8 +54,25 @@ public interface Neo4jPersistentProperty extends PersistentProperty getAnnotations(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java index 7583117b1..10e6f8c29 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java @@ -16,9 +16,22 @@ package org.springframework.data.neo4j.support; +import static org.springframework.data.neo4j.support.ParameterCheck.*; + +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.validation.Validator; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.neo4j.graphdb.*; +import org.neo4j.graphdb.DynamicRelationshipType; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Path; +import org.neo4j.graphdb.PropertyContainer; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.traversal.TraversalDescription; import org.neo4j.helpers.collection.ClosableIterable; @@ -57,12 +70,6 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; -import javax.annotation.PostConstruct; -import javax.validation.Validator; -import java.util.Map; - -import static org.springframework.data.neo4j.support.ParameterCheck.notNull; - /** * Mediator class for the graph related services like the {@link GraphDatabaseService}, the used * {@link org.springframework.data.neo4j.core.TypeRepresentationStrategy}, entity instantiators for nodes and relationships as well as a spring conversion service. @@ -424,7 +431,7 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister { Index relationshipIndex = infrastructure.getGraphDatabase().createIndex(Relationship.class, indexName, IndexType.SIMPLE); relationshipIndex.add((Relationship) element, field, value); } else if (element instanceof Node) { - infrastructure.getGraphDatabase().createIndex(Node.class, indexName, IndexType.SIMPLE).add((Node) element, field, value); + infrastructure.getIndexProvider().createIndex(Node.class, indexName, IndexType.SIMPLE).add((Node) element, field, value); } else { throw new IllegalArgumentException("Provided element is neither node nor relationship " + element); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProvider.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProvider.java index 6011e0b23..d7a7a011f 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProvider.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProvider.java @@ -21,25 +21,41 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; public interface IndexProvider { - public abstract Index getIndex(Class type); + Index getIndex(Class type); - public abstract Index getIndex(Class type, String indexName); + Index getIndex(Class type, String indexName); @SuppressWarnings("unchecked") - public abstract Index getIndex(Class type, String indexName, + Index getIndex(Class type, String indexName, IndexType indexType); @SuppressWarnings("unchecked") - public abstract Index getIndex(String indexName); + Index getIndex(String indexName); - public abstract boolean isNode(Class type); + boolean isNode(Class type); // TODO handle existing indexes @SuppressWarnings("unchecked") - public abstract Index createIndex(Class type, String indexName, + Index createIndex(Class type, String indexName, IndexType fullText); - public abstract Index getIndex(Neo4jPersistentProperty property, + Index getIndex(Neo4jPersistentProperty property, final Class instanceType); + /** + * adjust your indexName for the "__types__" indices + * + * @param type + * @return prefixed indexName for Type + */ + String createIndexValueForType(Class type); + + /** + * possibility to do something with the high level index name + * + * @param indexName + * @param type + * @return + */ + String customizeIndexName(String indexName, Class type); } \ No newline at end of file diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProviderImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProviderImpl.java index 0aff8c38a..7e7f164da 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProviderImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProviderImpl.java @@ -58,16 +58,16 @@ public class IndexProviderImpl implements IndexProvider { } final Neo4jPersistentEntityImpl persistentEntity = mappingContext.getPersistentEntity(type); - if (indexName == null) indexName = Indexed.Name.get(type); + if (indexName == null) indexName = customizeIndexName(Indexed.Name.get(type), type); final boolean useExistingIndex = indexType == null; if (useExistingIndex) { - if (persistentEntity.isNodeEntity()) return (Index) graphDatabase.getIndex(indexName); - if (persistentEntity.isRelationshipEntity()) return (Index) graphDatabase.getIndex(indexName); + if (persistentEntity.isNodeEntity() || persistentEntity.isRelationshipEntity()) return (Index) graphDatabase.getIndex(indexName); throw new IllegalArgumentException("Wrong index type supplied: " + type + " expected Node- or Relationship-Entity"); } - if (persistentEntity.isNodeEntity()) return (Index) createIndex(Node.class, indexName, indexType); + if (persistentEntity.isNodeEntity()) + return (Index) createIndex(Node.class, indexName, indexType); if (persistentEntity.isRelationshipEntity()) return (Index) createIndex(Relationship.class, indexName, indexType); throw new IllegalArgumentException("Wrong index type supplied: " + type + " expected Node- or Relationship-Entity"); @@ -99,12 +99,23 @@ public class IndexProviderImpl implements IndexProvider { final Class declaringType = property.getOwner().getType(); final String providedIndexName = indexedAnnotation==null || indexedAnnotation.indexName().isEmpty() ? null : indexedAnnotation.indexName(); final Indexed.Level level = indexedAnnotation == null ? Indexed.Level.CLASS : indexedAnnotation.level(); - String indexName = Indexed.Name.get(level, declaringType, providedIndexName, instanceType); + String indexName = customizeIndexName(Indexed.Name.get(level, declaringType, providedIndexName, instanceType), instanceType); if (!property.isIndexed() || property.getIndexInfo().getIndexType() == IndexType.SIMPLE) { return getIndex(declaringType, indexName, IndexType.SIMPLE); } - String defaultIndexName = Indexed.Name.get(level, declaringType, null, instanceType.getClass()); + String defaultIndexName = customizeIndexName(Indexed.Name.get(level, declaringType, null, instanceType.getClass()), instanceType); if (providedIndexName==null || providedIndexName.equals(defaultIndexName)) throw new IllegalStateException("Index name for "+property+" must differ from the default name: "+defaultIndexName); return getIndex(declaringType, indexName, property.getIndexInfo().getIndexType()); } + + @Override + public String createIndexValueForType(Class type) { + return type.getName(); + } + + @Override + public String customizeIndexName(String indexName, Class type) { + return indexName; + } + } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java index 393b648f1..b09413e8a 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java @@ -196,6 +196,14 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty getNodeTypesIndex() { - return graphDb.createIndex(Node.class,INDEX_NAME, IndexType.SIMPLE); - } + private Index getNodeTypesIndex() { + return graphDb.createIndex(Node.class, INDEX_NAME, IndexType.SIMPLE); + } - - @Override - public void postEntityCreation(Node state, Class type) { + @Override + public void postEntityCreation(Node state, Class type) { addToNodeTypesIndex(state, type); state.setProperty(TYPE_PROPERTY_NAME, type.getName()); - } + } private void addToNodeTypesIndex(Node node, Class entityClass) { - Class klass = entityClass; - while (klass.getAnnotation(NodeEntity.class) != null) { - getNodeTypesIndex().add(node, INDEX_KEY, klass.getName()); - klass = klass.getSuperclass(); - } - } + Class klass = entityClass; + + while (klass.getAnnotation(NodeEntity.class) != null) { + String value = klass.getName(); + if (indexProvider != null) + value = indexProvider.createIndexValueForType(klass); + + getNodeTypesIndex().add(node, INDEX_KEY, value); + klass = klass.getSuperclass(); + } + } @Override public ClosableIterable findAll(Class clazz) { @@ -64,9 +72,13 @@ public class IndexingNodeTypeRepresentationStrategy implements NodeTypeRepresent } private ClosableIterable findAllNodeBacked(Class clazz) { - final IndexHits allEntitiesOfType = getNodeTypesIndex().get(INDEX_KEY, clazz.getName()); + String value = clazz.getName(); + if (indexProvider != null) + value = indexProvider.createIndexValueForType(clazz); + + final IndexHits allEntitiesOfType = getNodeTypesIndex().get(INDEX_KEY, value); return new ClosableIndexHits(allEntitiesOfType); - } + } @Override public long count(Class entityClass) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/IndexingRelationshipTypeRepresentationStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/IndexingRelationshipTypeRepresentationStrategy.java index 802d2a11b..179a99f44 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/IndexingRelationshipTypeRepresentationStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/IndexingRelationshipTypeRepresentationStrategy.java @@ -24,6 +24,7 @@ import org.springframework.data.neo4j.annotation.RelationshipEntity; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.core.RelationshipTypeRepresentationStrategy; import org.springframework.data.neo4j.support.index.ClosableIndexHits; +import org.springframework.data.neo4j.support.index.IndexProvider; import org.springframework.data.neo4j.support.index.IndexType; public class IndexingRelationshipTypeRepresentationStrategy implements RelationshipTypeRepresentationStrategy { @@ -31,11 +32,14 @@ public class IndexingRelationshipTypeRepresentationStrategy implements Relations public static final String INDEX_NAME = "__rel_types__"; public static final String TYPE_PROPERTY_NAME = "__type__"; public static final String INDEX_KEY = "className"; - private GraphDatabase graphDb; + private final GraphDatabase graphDb; private final EntityTypeCache typeCache; + + private final IndexProvider indexProvider; - public IndexingRelationshipTypeRepresentationStrategy(GraphDatabase graphDb) { + public IndexingRelationshipTypeRepresentationStrategy(GraphDatabase graphDb, IndexProvider indexProvider) { this.graphDb = graphDb; + this.indexProvider = indexProvider; typeCache = new EntityTypeCache(); } @@ -50,12 +54,16 @@ public class IndexingRelationshipTypeRepresentationStrategy implements Relations } private void addToTypesIndex(Relationship node, Class entityClass) { - Class type = entityClass; - while (type.getAnnotation(RelationshipEntity.class) != null) { - getRelTypesIndex().add(node, INDEX_KEY, type.getName()); - type = type.getSuperclass(); - } - } + Class type = entityClass; + while (type.getAnnotation(RelationshipEntity.class) != null) { + String value = entityClass.getName(); + if (indexProvider != null) + value = indexProvider.createIndexValueForType(entityClass); + + getRelTypesIndex().add(node, INDEX_KEY, value); + type = type.getSuperclass(); + } + } @Override public ClosableIterable findAll(Class clazz) { @@ -63,7 +71,11 @@ public class IndexingRelationshipTypeRepresentationStrategy implements Relations } private ClosableIterable findAllRelBacked(Class clazz) { - final IndexHits allEntitiesOfType = getRelTypesIndex().get(INDEX_KEY, clazz.getName()); + String value = clazz.getName(); + if (indexProvider != null) + value = indexProvider.createIndexValueForType(clazz); + + final IndexHits allEntitiesOfType = getRelTypesIndex().get(INDEX_KEY, value); return new ClosableIndexHits(allEntitiesOfType); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategyFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategyFactory.java index a647033da..de3a25ad3 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategyFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategyFactory.java @@ -23,19 +23,32 @@ import org.neo4j.graphdb.index.Index; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy; import org.springframework.data.neo4j.core.RelationshipTypeRepresentationStrategy; +import org.springframework.data.neo4j.support.index.IndexProvider; import org.springframework.data.neo4j.support.index.NoSuchIndexException; public class TypeRepresentationStrategyFactory { - private GraphDatabase graphDatabaseService; - private Strategy strategy; + private final GraphDatabase graphDatabaseService; + private final Strategy strategy; + private IndexProvider indexProvider; public TypeRepresentationStrategyFactory(GraphDatabase graphDatabaseService) { - this(graphDatabaseService,chooseStrategy(graphDatabaseService)); + this(graphDatabaseService,chooseStrategy(graphDatabaseService), null); } + + public TypeRepresentationStrategyFactory(GraphDatabase graphDatabaseService, IndexProvider indexProvider) { + this(graphDatabaseService,chooseStrategy(graphDatabaseService), indexProvider); + } + public TypeRepresentationStrategyFactory(GraphDatabase graphDatabaseService,Strategy strategy) { this.graphDatabaseService = graphDatabaseService; this.strategy = strategy; } + + public TypeRepresentationStrategyFactory(GraphDatabase graphDatabaseService,Strategy strategy, IndexProvider indexProvider) { + this.indexProvider = indexProvider; + this.graphDatabaseService = graphDatabaseService; + this.strategy = strategy; + } private static Strategy chooseStrategy(GraphDatabase graphDatabaseService) { if (isAlreadyIndexed(graphDatabaseService)) return Strategy.Indexed; @@ -62,50 +75,54 @@ public class TypeRepresentationStrategyFactory { } public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy() { - return strategy.getNodeTypeRepresentationStrategy(graphDatabaseService); + return strategy.getNodeTypeRepresentationStrategy(graphDatabaseService, indexProvider); } public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy() { - return strategy.getRelationshipTypeRepresentationStrategy(graphDatabaseService); + return strategy.getRelationshipTypeRepresentationStrategy(graphDatabaseService, indexProvider); + } + + public void setIndexProvider(IndexProvider indexProvider) { + this.indexProvider = indexProvider; } private enum Strategy { SubRef { @Override - public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService) { + public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) { return new SubReferenceNodeTypeRepresentationStrategy(graphDatabaseService); } @Override - public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService) { + public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) { return new NoopRelationshipTypeRepresentationStrategy(); } }, Indexed { @Override - public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService) { - return new IndexingNodeTypeRepresentationStrategy(graphDatabaseService); + public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) { + return new IndexingNodeTypeRepresentationStrategy(graphDatabaseService, indexProvider); } @Override - public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService) { - return new IndexingRelationshipTypeRepresentationStrategy(graphDatabaseService); + public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) { + return new IndexingRelationshipTypeRepresentationStrategy(graphDatabaseService, indexProvider); } }, Noop { @Override - public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService) { + public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) { return new NoopNodeTypeRepresentationStrategy(); } @Override - public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService) { + public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) { return new NoopRelationshipTypeRepresentationStrategy(); } }; - public abstract NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService); + public abstract NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider); - public abstract RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService); + public abstract RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider); } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jEntityConverterTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jEntityConverterTest.java index d9e832ab9..d0e328b07 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jEntityConverterTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jEntityConverterTest.java @@ -26,6 +26,7 @@ import org.springframework.data.neo4j.model.Friendship; import org.springframework.data.neo4j.model.Group; import org.springframework.data.neo4j.model.Person; import org.springframework.data.neo4j.model.Personality; +import org.springframework.transaction.annotation.Transactional; import java.util.Collection; import java.util.Date; @@ -151,6 +152,33 @@ public class Neo4jEntityConverterTest extends Neo4jPersistentTestBase { assertEquals(Personality.EXTROVERT, p.getPersonality()); } + private void neo4jPropertyTest(final T value) { + michael.setDynamicProperty(value); + storeInGraph(michael); + final Person loaded = template.findOne(michael.getId(), Person.class); + assertEquals(value, loaded.getDynamicProperty()); + } + + @Test + public void testIntProperty() { + neo4jPropertyTest(123); + } + + @Test + public void testDoubleProperty() { + neo4jPropertyTest(3.1415); + } + + @Test + public void testBooleanProperty() { + neo4jPropertyTest(true); + } + + @Test + public void testIntegerProperty() { + neo4jPropertyTest(Integer.valueOf(3)); + } + @Test public void testDeleteProperty() { final Node existingNode = createNewNode(); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java index 01a2a6925..3d7b2ccd7 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java @@ -51,6 +51,8 @@ public class Person { @Indexed private int age; + private Object dynamicProperty; + private Short height; private transient String thought; @@ -261,6 +263,14 @@ public class Person { this.nickname = nickname; } + public void setDynamicProperty(Object dynamicProperty) { + this.dynamicProperty = dynamicProperty; + } + + public Object getDynamicProperty() { + return dynamicProperty; + } + public Person(Long graphId) { this.graphId = graphId; } diff --git a/src/docbkx/reference/aspectj-details.xml b/src/docbkx/reference/aspectj-details.xml index ccca1cab4..62b3962dc 100644 --- a/src/docbkx/reference/aspectj-details.xml +++ b/src/docbkx/reference/aspectj-details.xml @@ -3,7 +3,7 @@ AspectJ details - The object graph mapper of Spring Data Neo4j relies heavily on AspectJ. AspectJ is a Java implementation + The advanced mapping mode of Spring Data Neo4j relies heavily on AspectJ. AspectJ is a Java implementation of the aspect-oriented programming paradigm that allows easy extraction and controlled application of so-called cross-cutting concerns. Cross-cutting concerns are typically repetitive tasks in a system (e.g. logging, diff --git a/src/docbkx/reference/attachdetach.key b/src/docbkx/reference/attachdetach.key new file mode 100644 index 000000000..0d3f7d67f Binary files /dev/null and b/src/docbkx/reference/attachdetach.key differ diff --git a/src/docbkx/reference/attachdetach.png b/src/docbkx/reference/attachdetach.png new file mode 100644 index 000000000..4d535acc6 Binary files /dev/null and b/src/docbkx/reference/attachdetach.png differ diff --git a/src/docbkx/reference/cross-store.xml b/src/docbkx/reference/cross-store.xml index ae617c0de..b94389069 100644 --- a/src/docbkx/reference/cross-store.xml +++ b/src/docbkx/reference/cross-store.xml @@ -3,7 +3,8 @@ Cross-store persistence - The Spring Data Neo4j project support cross-store persistence, which allows for parts of the data to be + The Spring Data Neo4j project support cross-store persistence for the advanced mapping mode, + which allows for parts of the data to be stored in a traditional JPA data store (RDBMS), and other parts in a graph store. This means that an entity can be partially stored in e.g. MySQL, and partially stored in Neo4j. @@ -23,7 +24,7 @@ A backing node in the graph store is only created when the entity has been assigned a JPA ID. Only then will the association between the two stores be established. Until the entity has been persisted, its state is just kept inside the POJO (in detached state), and then flushed to the backing graph - database on persist(). + database on the persist operation. The association between the two entities is maintained via a FOREIGN_ID field in the node, that diff --git a/src/docbkx/reference/neo4j.xml b/src/docbkx/reference/neo4j.xml index ce0ec0e27..8ee9197fc 100644 --- a/src/docbkx/reference/neo4j.xml +++ b/src/docbkx/reference/neo4j.xml @@ -160,7 +160,8 @@ for (Node foundNode : nodeIndex.get("property","value")) { "Cypher" which draws from many sources. It resembles SQL but with an iconic representation of patterns in the graph (concepts drawn from SPARQL). The Cypher execution engine was written in Scala to leverage the high expressiveness for lazy sequence operations of the language and the - parser combinator library. + parser combinator library. A screencast explaining the possibilities in detail can be found on the + Neo4j video site. Cypher queries always begin with a start set of nodes. Those can be either expressed by their diff --git a/src/docbkx/reference/performance.xml b/src/docbkx/reference/performance.xml index 10b2b4eaa..501f53786 100644 --- a/src/docbkx/reference/performance.xml +++ b/src/docbkx/reference/performance.xml @@ -8,7 +8,7 @@ of using Spring Data Neo4j instead of the Neo4j API directly.
- When is Spring Data Neo4j right + When to use Spring Data Neo4j The focus of Spring Data Neo4j is to add a convenience layer on top of the Neo4j API. This enables developers to get up and running with a graph database very quickly, having their domain objects @@ -16,23 +16,38 @@ more efficient ways to explore and process the graph - if the performance requirements demand it. - Like any other object mapping framework, the domain entities that are created, read, or persisted + Like with any other object mapping framework, the domain entities that are created, read, or persisted represent only a small fraction of the data stored in the database. This is the set needed for a certain use-case to be displayed, edited or processed in a low throughput fashion. The main advantages of using an object mapper in this case are the ease of use of real domain objects in your business - logic and also with existing + logic and also the integration with existing frameworks and libraries that expect Java POJOs as input or create them as results. Spring Data Neo4j, however, was not designed with a major focus on performance. It does add some overhead - to pure graph operations. Something to keep in mind is that any access of properties and relationships - will in general read through down to the database. To avoid multiple reads, it is sensible to store the - result in a local variable in suitable scope (e.g. method, class or jsp). - + to pure graph operations. + Most of the overhead comes from the use of the Java Reflection API, which is used to provide information about annotations, fields and constructors. Some of the information is already cached - by the JVM and the library, so that only the first access gets a performance penalty. + by the JVM and the library infrastructure from Spring-Data-Commons, so that only the first access gets a performance penalty. + Other reflection penalties like field or method access will occur all the time. + + For the simple mapping it is important to be aware of the size graph of data that is pulled out of the + graph database in a single read and copied to domain entities. That's why Spring Data Neo4j loads + related data not by default. You have to provide an indicator (@Fetch) to do so. Alternatively + the Neo4jTemplate.fetch method offers means of of loading entities and collections of those. + + + For the advanced mapping mode keep in mind that any access of properties and relationships + will in general read through down to the database. To avoid multiple reads, it is sensible to store the + result in a local variable in suitable scope (e.g. method, class or jsp). + + + To evaluate if the performance of Spring Data Neo4j impacts a certain use-case it is sensible to define + performance requirements and measure the actual time in realistic test scenarios for the use-case. Only if + Spring Data Neo4j doesn't perform as fast as required it is recommended to drop down to the native Neo4j API. +
diff --git a/src/docbkx/reference/programming-model/attachdetach.xml b/src/docbkx/reference/programming-model/attachdetach.xml index 42e558b77..a8ea87689 100644 --- a/src/docbkx/reference/programming-model/attachdetach.xml +++ b/src/docbkx/reference/programming-model/attachdetach.xml @@ -1,24 +1,35 @@
- Detached node entities + Detached node entities in advanced mapping mode + + This section only applies to the advanced mapping (AspectJ-backed). The simple mapping always detaches entities on + load as it copies the data out of the graph into the entities and stores it back fully too. + Node entities can be in two different persistence states: attached or detached. By default, newly created node - entities are in the detached state. When persist() is called on the entity, it becomes + entities are in the detached state. When persist() or template.save() is called on the entity, it becomes attached to the graph, and its properties and relationships are stores in the database. If - persist() is not called within a transaction, it automatically creates an implicit - transaction for the operation. + the save operation is not called within a transaction, it automatically creates an implicit + transaction only for the operation. Changing an attached entity inside a transaction will immediately write through the changes to the datastore. Whenever an entity is changed outside of a transaction it becomes detached. The - changes are stored in the entity itself until the next call to persist(). + changes are stored in the entity (its fields) itself until the next call to a save operation. All entities returned by library functions are initially in an attached state. Just as with any other entity, changing them outside of a transaction detaches them, and they must be reattached with persist() for the data to be saved. + + + + + + + @@ -82,7 +93,7 @@ movie.setTopActor(actor); It is a matter of Java references and is not related to the data model in the database. - The persist operation (merge) stores all properties of the entity to the graph database + The save operation (merge) stores all properties of the entity to the graph database and puts the entity in attached mode. There is no need to update the reference to the Java POJO as the underlying backing node handles the read-through transparently. If multiple object instances that point to the same node are persisted, the ordering is not important diff --git a/src/docbkx/reference/programming-model/beanvalidation.xml b/src/docbkx/reference/programming-model/beanvalidation.xml index 989b4060a..e3fc8e3f8 100644 --- a/src/docbkx/reference/programming-model/beanvalidation.xml +++ b/src/docbkx/reference/programming-model/beanvalidation.xml @@ -3,11 +3,12 @@
Bean validation (JSR-303) - Spring Data Neo4j supports property-based validation support. When a property is changed, it is + Spring Data Neo4j supports property-based validation support. When a property is changed and persisted, it is checked against the annotated constraints, e.g. @Min, @Max, @Size, etc. Validation errors throw a ValidationException. The validation support that comes with Spring is used for evaluating the constraints. To use this feature, a validator - has to be registered with the GraphDatabaseContext. + has to be registered with the Neo4jTemplate, which is done automatically by the Neo4jConfiguration + if one is present in the Spring Config. Bean validation diff --git a/src/docbkx/reference/programming-model/conversion.xml b/src/docbkx/reference/programming-model/conversion.xml new file mode 100644 index 000000000..d1efd64fc --- /dev/null +++ b/src/docbkx/reference/programming-model/conversion.xml @@ -0,0 +1,16 @@ + + +
+ Conversion + + Neo4jTemplate has a generic convert method which might also use projection underneath. + The same conversion facilities that are used by default in the result handling DSL are offered here for individual use. + + + Supported conversions are: Nodes to Paths and to Entities, Relationships to Paths and to Entities, Paths to Node (EndNode), + Relationships (LastRelationship) and to EntityPaths. Entities to Nodes or Relationships. + + + It is also possible to provide a custom ResultConverter that additionally takes care of conversions. + +
\ No newline at end of file diff --git a/src/docbkx/reference/programming-model/indexing.xml b/src/docbkx/reference/programming-model/indexing.xml index 5faa250c9..f8a6fb8e7 100644 --- a/src/docbkx/reference/programming-model/indexing.xml +++ b/src/docbkx/reference/programming-model/indexing.xml @@ -3,10 +3,16 @@
Indexing + + Indexing is used in Neo4j to quickly find nodes and relationships to start graph operations from. + Either for manually traversing the graph, using the traversal framework, cypher or gremlin queries + or for "global" graph operations. Indexes are also employed to ensure uniqueness of elements with + certain properties. + - The Neo4j graph database can use different so-called index providers for exact lookups and fulltext + The Neo4j graph database employs different index providers for exact lookups and fulltext searches. Lucene is the default index provider implementation. Each named index is configured to be - fulltext or exact. + fulltext or exact. There is also a spatial index provider for geo-searches.
@@ -23,20 +29,30 @@ other fields are indexed with their string representation. - The @Indexed annotation also provides the option of using a custom index. The default index + The @Indexed annotation also provides the option of using a custom index name. The default index name is the simple class name of the entity, so that each class typically gets its own index. It is recommended to not have two entity classes with the same class name, regardless of package. + + If a field is declared in a superclass but different indexes for subclasses are needed, the + level attribute declares what will be used as index. Level.CLASS + uses the class where the field was declared and Level.INSTANCE uses the class + that is provided or of the actual entity instance. + The indexes can be queried by using a repository (see ). - Typically, the repository is an instance of - org.springframework.data.neo4j.repository.DirectGraphRepositoryFactory. + The repository is an instance of + org.springframework.data.neo4j.repository.IndexRepository. The methods findByPropertyValue() and findAllByPropertyValue() work on the exact indexes and return the first or all matches. To do range queries, use findAllByRange() (please note that currently both values are inclusive). + + For providing explicit index names the repository has to extend NamedIndexRepository. + This adds the shown methods with another signature that take the index name as first parameter. + Indexing entities Spring Data Neo4j also supports fulltext indexes. By default, indexed fields are stored in an exact lookup index. To have them analyzed and prepared for fulltext search, the - @Indexed annotation has the boolean fulltext attribute. + @Indexed annotation has the type attribute which can be set to IndexType.FULLTEXT. Please note that fulltext indexes require a separate index name as the fulltext configuration is stored in the index itself. @@ -72,7 +88,7 @@ for (Person middleAgedDeveloper : graphRepository.findAllByRange("age", 20, 40)) Access to the fulltext index is provided by the findAllByQuery() repository method. Wildcards like * are allowed. Generally though, the fulltext querying rules of the underlying index provider apply. See the - Lucene documentation for more + Lucene documentation for more information on this. @@ -101,41 +117,73 @@ Person mark = graphRepository.findAllByQuery("people-search", "name", "ma*");
Manual index access - The index for a domain class is also available from GraphDatabaseContext via + The index for a domain class is also available from Neo4jTemplate via the getIndex() method. The second parameter is optional and takes the index name if it should not be inferred from the class name. It returns the index implementation that is provided by Neo4j. - Manual index usage - Manual index retrieval by type and name + personIndex = gdc.getIndex(Person.class); +Index personIndex = template.getIndex(null, Person.class); personIndex.query(new QueryContext(NumericRangeQuery.newÍntRange("age", 20, 40, true, true)) .sort(new Sort(new SortField("age", SortField.INT, false)))); // Named index -Index namedPersonIndex = gdc.getIndex(Person.class, "people"); +Index namedPersonIndex = template.getIndex("people",Person.class); namedPersonIndex.get("name", "Mark"); // Fulltext index -Index personFulltextIndex = gdc.getIndex(Person.class, "people-search", true); +Index personFulltextIndex = template.getIndex("people-search", Person.class); +personFulltextIndex.query("name", "*cha*"); +personFulltextIndex.query("{name:*cha*}"); +]]> + + + It is also possible to pass in the property name of the entity with an @Indexed annotation whose + index should be returned. + + + Manual index retrieval by property configuration + personIndex = template.getIndex(Person.class, "age"); +personIndex.query(new QueryContext(NumericRangeQuery.newÍntRange("age", 20, 40, true, true)) + .sort(new Sort(new SortField("age", SortField.INT, false)))); + +// Fulltext index +Index personFulltextIndex = template.getIndex(Person.class,"name"); personFulltextIndex.query("name", "*cha*"); personFulltextIndex.query("{name:*cha*}"); ]]>
- Indexing in Neo4jTemplate - - Neo4jTemplate also offers index support, providing auto-indexing for fields at creation time. - There is an autoIndex method that can also add indexes for a set of fields in one go. - + Index queries in Neo4jTemplate For querying the index, the template offers query methods that take either the exact match - parameters or a query object/expression, and push the results wrapped uniformly as Paths to - the supplied PathMapper to be converted or collected. + parameters or a query object/expression, return the results as Result objects which + then can be converted and projected further using the result-conversion-dsl (see ).
+
+ Neo4j Auto Indexes + + Neo4j allows to configure auto-indexing + for certain properties on nodes and relationships. This auto-indexing differs + from the approach used in Spring Data Neo4j because it only updates the indexes when the transaction is committed. So the + index modifications will only be available after the successful commit. + It is possible to use the specific index names node_auto_index and relationship_auto_index when + querying indexes in Spring Data Neo4j either with the query methods in template and repositories or via Cypher and Gremlin. + +
+
+ Spatial Indexes + + Spring Data Neo4j offers limited support for spatial queries using the neo4j-spatial library. See the + separate chapter for details. + +
diff --git a/src/docbkx/reference/programming-model/introducedmethods.xml b/src/docbkx/reference/programming-model/introducedmethods.xml index cf93ea093..96be16a13 100644 --- a/src/docbkx/reference/programming-model/introducedmethods.xml +++ b/src/docbkx/reference/programming-model/introducedmethods.xml @@ -1,8 +1,14 @@
- Introduced methods + Active Record Methods for Advanced Mapping Mode + This chapter only applies to the advanced mapping. Currently the Aspects introduce the following + methods by default, this will change in the future, there will be separate Mixin-Interfaces that + can selectively mixed into the domain entities if needed. Otherwise the AspectJ interaction will + be restricted to field access interception and post constructor handling. + + The node and relationship aspects introduce (via AspectJ ITD - inter type declaration) several methods to the entities. @@ -114,7 +120,7 @@ - Executes the given query, providing the {self} variable with the node-id and returning the results converted to the target type. + Executes the given Cypher query, providing the {self} variable with the node-id and returning the results converted to the target type. <T> Iterable<T> NodeBacked.findAllByQuery(final String query, final Class<T> targetType) diff --git a/src/docbkx/reference/programming-model/node-entities.xml b/src/docbkx/reference/programming-model/node-entities.xml index ed7c4b08f..83748f340 100644 --- a/src/docbkx/reference/programming-model/node-entities.xml +++ b/src/docbkx/reference/programming-model/node-entities.xml @@ -39,6 +39,26 @@ public class Movie {
+
+ @GraphId: Neo4j -id field + + For the simple mapping this is a required field which must be of type Long. It is used + by Spring Data Neo4j to store the node or relationship-id to re-connect the entity to the graph. + + + + It must not be a primitive type because then the "non-attached" case can not be represented as the + default value 0 would point to the reference node. Please make also sure that + an equals() and hashCode() method have to be provided which take the id + field into account (and also handle the "non-attached", null case). + + + + For the advanced mapping such a field is optional. Only if the underlying id has to be accessed, it is + needed. + +
+
@GraphProperty: Optional annotation for property fields diff --git a/src/docbkx/reference/programming-model/projection.xml b/src/docbkx/reference/programming-model/projection.xml index 3b8bfac1d..71545eac8 100644 --- a/src/docbkx/reference/programming-model/projection.xml +++ b/src/docbkx/reference/programming-model/projection.xml @@ -16,8 +16,8 @@ context and only offers the attributes and methods needed here would be very beneficial. Spring Data Neo4j offers initial support for projecting node and relationship entities to different target - types. All instances of this projected entity share the same backing node or relationship, so data changes are - reflected immediately. + types. All instances of this projected entity share the same backing node or relationship, so changes are + reflected on the same data. This could for instance also be used to handle nodes of a traversal with a unified (simpler) type (e.g. for @@ -41,6 +41,4 @@ for (Person person : graphRepository.findAllByPropertyValue("occupation","develo } ]]> - -
diff --git a/src/docbkx/reference/programming-model/repositories.xml b/src/docbkx/reference/programming-model/repositories.xml index 3b7f837a5..d7cb32783 100644 --- a/src/docbkx/reference/programming-model/repositories.xml +++ b/src/docbkx/reference/programming-model/repositories.xml @@ -4,37 +4,32 @@ CRUD with repositories The repositories provided by Spring Data Neo4j build on the composable repository infrastructure - in Spring Data Commons. + in Spring Data Commons. They allow for interface based composition of repositories consisting of provided default implementations for certain interfaces and additional custom implementations for other methods. - - - - - - - - - - - Spring Data Neo4j repositories support annotated and named queries for the Neo4j - Cypher query-language. + Cypher query-language and + Gremlin graph DSL. Spring Data Neo4j comes with typed repository implementations that provide methods for - locating node and relationship entities. There are 3 types of basic repository interfaces + locating node and relationship entities. There are several types of basic repository interfaces and implementations. CRUDRepository provides basic operations, IndexRepository and NamedIndexRepository delegate to Neo4j's internal indexing subsystem for queries, and TraversalRepository handles Neo4j traversals. - GraphRepository is a convenience repository interface, extending CRUDRepository, + With the RelationshipOperationsRepository it is possible to access, create and delete + relationships between entitites or nodes. + The SpatialRepository allows geographic searches () + + + GraphRepository is a convenience repository interface, combining CRUDRepository, IndexRepository, and TraversalRepository. Generally, it has all the - desired repository methods. If named index operations are required, then NamedIndexRepository - may also be included. + desired repository methods. If other operations are required then the additional repository interfaces should + be added to the individual interface declaration.
@@ -45,41 +40,38 @@ for type based queries. - Load an instance via a Neo4j node id + Load an entity instance via an id T findOne(id) - Check for existence of a Neo4j node id + Check for existence of a id in the graph boolean exists(id) - Iterate over all nodes of a node entity type - Iterable<T> findAll() - (supported in future versions: - Iterable<T> findAll(Sort) and - Page<T> findAll(Pageable)) + Iterate over all entities instances of the repository entity type + + + Iterable<T> findAll() + Iterable<T> findAll(Sort) + Page<T> findAll(Pageable) + + - Count the instances of a node entity type + Count the instances of the repository entity type Long count() - Save a graph entity + Save entities T save(T) and Iterable<T> save(Iterable<T>) - Delete a graph entity + Delete graph entities void delete(T), void; delete(Iterable<T>), and deleteAll() - - Important to note here is that the save, delete, and deleteAll - methods are only there to conform to the org.springframework.data.repository.Repository - interface. The recommended way of saving and deleting entities is by using entity.persist() - and entity.remove(). -
@@ -128,30 +120,33 @@
- Cypher queries + Query and Finder Methods
Annotated queries Queries using the Cypher graph query language can be supplied with the @Query annotation. - That means every method annotated with @Query("start n=(%node) match (n)-->(m) return m") - will use the supplied query string. The named parameter %node will be replaced by the actual method parameters. - Node and Relationship-Entities are resolved to their respective id's and all other parameters are + That means every method annotated with @Query("start n=node:IndexName(key={node or 0}) match (n)-->(m) return m") + will use the supplied query string. The named or indexed parameter {node} will be substituted by the actual method parameter. + Node and Relationship-Entities are handled directly, Iterables thereof as well. All other parameters are replaced directly (i.e. Strings, Longs, etc). There is special support for the Sort and Pageable parameters from Spring Data Commons, which are supported to add programmatic paging and sorting (alternatively static paging and sorting can be supplied in the query string itself). For using the named parameters you have to either annotate the parameters of the method with the - @Param("node") annotation or enable debug symbols. + @Param("node") annotation or enable debug symbols. Indexed parameters are always usable. + + Gremlin queries can be used similarly, the @Query annotation would just need a type=QueryType.GREMLIN attribute. + Parameters are supported in the same way. +
Named queries Spring Data Neo4j also supports the notion of named queries which are externalized in property-config-files (META-INF/neo4j-named-queries.properties). Those files have the format: - Entity.finderName=query (e.g. Person.findBoss=start p=({p_person}) match (p)<-[:BOSS]-(boss) return boss). - Otherwise named queries support the same parameters as annotated queries. For using the named parameters you have to either - annotate the parameters of the method with the @Param("p_person") annotation or enable debug symbols. + Entity.finderName=query (e.g. Person.findBoss=start p=node({0}) match (p)<-[:BOSS]-(boss) return boss). + Otherwise named queries support the same parameters as annotated queries.
@@ -163,7 +158,7 @@
Cypher examples - There is a screencast available showing many features of the query language. + There is a screencast available showing many features of the query language. The following examples are taken from the cineasts dataset of the tutorial section. @@ -189,36 +184,118 @@
+
+ Queries derived from finder-method names + As known from Rails or Grails it is possible to derive queries for domain entities from finder method names + like Iterable<Person> findByNameAndAgeGreaterThan(String name, int age). + + Using the infrastructure in Spring Data Commons that allows to collect the meta information about entities and their properties a finder method + name can be split into its semantic parts and converted into a cypher query. + @Indexed fields will be converted into index-lookups of the start clause, + navigation along relationships will be reflected in the match clause properties with operators will end up as expressions in the + where clause. Order and limiting of the query will by handled by provided Pageable or parameters. + The other parameters will be used in the order they appear in the method signature so that should align with the expressions stated in the method name. + + + + Some examples of methods and resulting cypher queries of a PersonRepository + { + +// start person=node:Person(id={0}) return person +Person findById(String id) + +// start person=node:Person({0}) return person - {0} will be "id:"+name +Iterable findByNameLike(String name) + +// start person=node:__types__("className"="com...Person") +// where person.age = {0} and person.married = {1} +// return person +Iterable findByAgeAndMarried(int age, boolean married) + +// start person=node:__types__("className"="com...Person") +// match person<-[:CHILD]-parent +// where parent.age > {0} and person.married = {1} +// return person +Iterable findByParentAgeAndMarried(int age, boolean married) +} +]]> + + +
+
+ + CypherDSL repository + + Spring Data Neo4j supports the new cypher-dsl to write Cypher queries in a statically typed way. Just by including + CypherDslRepository to your repository you get the Page<T> query(Execute query, params, Pageable page) + and the EndResult<T> query(Execute query, params);. The result type of the Cypher-DSL builder is called + Execute + + + Examples for Cypher-DSL repository + + , + CypherDslRepository {} + + @Autowired PersonRepository repo; + // START company=node:Company(name={name}) MATCH company<-[:WORKS_AT]->person RETURN person + + Execute query = start( lookup( "company", "Company", "name", param("name") ) ). + match( path().from( "company" ).in( "WORKS_AT" ).to( "person" )). + returns( nodes( "person" )) + Page people = repo.query(query , map("name","Neo4j"), new PageRequest(1,10)); + + QPerson person = QPerson.person; + QCompany company = QCompany.company; + Execute query = start( lookup( company, "Company", company.name, param("name") ) ). + match( path().from( company ).in( "WORKS_AT" ).to( person ). + .where(person.firstName.like("P*").and(person.age.gt(25))). + returns( nodes( person )) + EndResult people = repo.query(query , map("name","Neo4j")); + + ]]> + +
+ + +
Creating repositories - The Repository instances are either created manually via a - DirectGraphRepositoryFactory, bound to a concrete node or relationship entity class. - The DirectGraphRepositoryFactory is configured in the Spring context and can be injected. + The Repository instances should normally be injected but can also be created manually via the + Neo4jTemplate. - Using GraphRepositories - graphRepository = template - .repositoryFor(Person.class); + Using basic GraphRepository methods + {} -Person michael = graphRepository.save(new Person("Michael", 36)); +@Autowired PersonRepository repo; +// OR +GraphRepository repo = template + .repositoryFor(Person.class); -Person dave = graphRepository.findOne(123); +Person michael = repo.save(new Person("Michael", 36)); -Long numberOfPeople = graphRepository.count(); +Person dave = repo.findOne(123); -Person mark = graphRepository.findByPropertyValue("name", "mark"); +Long numberOfPeople = repo.count(); -Iterable devs = graphRepository.findAllByProperyValue("occupation", "developer"); +Person mark = repo.findByPropertyValue("name", "mark"); -Iterable middleAgedPeople = graphRepository.findAllByRange("age", 20, 40); +Iterable devs = repo.findAllByProperyValue("occupation", "developer"); -Iterable aTeam = graphRepository.findAllByQuery("name", "A*"); +Iterable middleAgedPeople = repo.findAllByRange("age", 20, 40); -Iterable davesFriends = graphRepository.findAllByTraversal(dave, +Iterable aTeam = repo.findAllByQuery("name", "A*"); + +Iterable davesFriends = repo.findAllByTraversal(dave, Traversal.description().pruneAfterDepth(1) .relationships(KNOWS).filter(returnAllButStartNode())); ]]> @@ -237,6 +314,29 @@ Iterable davesFriends = graphRepository.findAllByTraversal(dave, Composing repositories , PersonRepositoryExtension {} +// configure the repositories, preferably via the neo4j:repositories namespace +// (template reference is optional) + + +// have it injected +@Autowired +PersonRepository personRepository; +// or created via the template +PersonRepository personRepository = template.repositoryFor(Person.class); + + +Person michael = personRepository.save(new Person("Michael",36)); + +Person dave=personRepository.findOne(123); + +Iterable devs = personRepository.findAllByPropertyValue("occupation","developer"); + +Iterable aTeam = graphRepository.findAllByQuery( "name","A*"); + +Iterable friends = personRepository.findFriends(dave); + + // alternatively select some of the required repositories individually public interface PersonRepository extends CRUDGraphRepository, IndexQueryExecutor, TraversalQueryExecutor, @@ -254,27 +354,14 @@ public class PersonRepositoryImpl implements PersonRepositoryExtension { return baseRepository.findAllByTraversal(person, friendsTraversal); } } - -// configure the repositories, preferably via the datagraph:repositories namespace -// (template reference is optional) - - -// have it injected -@Autowired -PersonRepository personRepository; - -Person michael = personRepository.save(new Person("Michael",36)); - -Person dave=personRepository.findOne(123); - -Iterable devs = personRepository.findAllByPropertyValue("occupation","developer"); - -Iterable aTeam = graphRepository.findAllByQuery( "name","A*"); - -Iterable friends = personRepository.findFriends(dave); ]]> - + + + If you use <context:component-scan> in your spring config, please make sure to put it behind + <neo4j:repositories>, as the RepositoryFactoryBean adds new bean definitions for all the declared + repositories, the context scan doesn't pick them up otherwise. + +
\ No newline at end of file diff --git a/src/docbkx/reference/programming-model/simple_mapping.xml b/src/docbkx/reference/programming-model/simple_mapping.xml index be19a3ac5..9658ea7c5 100644 --- a/src/docbkx/reference/programming-model/simple_mapping.xml +++ b/src/docbkx/reference/programming-model/simple_mapping.xml @@ -30,6 +30,13 @@ for classes. Both Neo4jPersistentEntitity as well as Neo4jPersistentProperty provide access to that information on their scope. + + + Please note that if you have two collections in an entity pointing to the same relationship and one of them has data and the other is + empty due to the nature of persisting it, one will override the other in the graph so that you might end up with no data. If you want + a relationship-collection to be ignored on save set it to null. + + Examples for loading entities from the graph diff --git a/src/docbkx/reference/programming-model/spatial.xml b/src/docbkx/reference/programming-model/spatial.xml index adfcbe35a..1766fe28b 100644 --- a/src/docbkx/reference/programming-model/spatial.xml +++ b/src/docbkx/reference/programming-model/spatial.xml @@ -4,7 +4,7 @@ Geospatial Queries SpatialRepository is a dedicated Repository for spatial queries. - Spring Data Neo4j provides an optional dependency to neo4j-spatial which is an advanced library + Spring Data Neo4j provides an optional dependency to neo4j-spatial which is an advanced library for GIS operations. So if you include the maven dependency in your pom.xml, Neo4j-Spatial and the required SPATIAL index provider is available. @@ -28,12 +28,12 @@ - Fields of + Fields of Well Known Text
+ Neo4jTemplate + The Neo4jTemplate offers the convenient API of Spring templates for the Neo4j graph - database. + database. The Spring Data Neo4j Object Graph mapping builds upon the core functionality of the + template to persist objects to the graph and load them in a variety of ways. + The template handles the active mapping mode () transparently. + + + Besides methods for creating, storing and deleting entities, nodes and relationships in the graph, Neo4jTemplate + also offers a wide range of query methods. To reduce the proliferation of query methods a simple result handling + DSL was added. @@ -17,8 +26,9 @@ ResultConverter<FROM,TO> which takes care of custom conversions. By default most query methods can already handle conversions from and to: Paths, Nodes, Relationship and GraphEntities as well as conversions backed by registered ConversionServices. A converted Result<FROM> is an - Iterable<TO>. Results can be limited to a single value using the result.single() - method. It also offers support for a pure callback function using a Handler<T>. + Iterable<TO>. Results can be limited to a single value using the + result.single() or result.singleOrNull() + methods. It also offers support for a pure callback function using a Handler<T>.
@@ -37,7 +47,7 @@ The traversal methods are at the core of graph operations. The traverse() method covers the full traversal operation that takes a - TraversalDescription (typically built with the Traversal.description() + TraversalDescription (typically built with the template.getGraphDatabase().traversalDescription() DSL) and runs it from the given start node. traverse returns a Result<Path> to be used or transformed. @@ -62,18 +72,21 @@
Transactions - The Neo4jTemplate provides configurable implicit transactions for all its methods. By - default it creates a transaction for each call (which is a no-op if there is already a transaction - running). If you call the constructor with the useExplicitTransactions parameter set to - true, it won't create any transactions so you have to provide them using @Transactional - or the TransactionTemplate. + The Neo4jTemplate provides implicit transactions for some of its methods. For instance + save uses them. For other modifying operations please provide Spring Transaction management + using @Transactional or the TransactionTemplate.
Neo4j REST Server - If the template is configured to use a RestGraphDatabase the expensive operations + If the template is configured to use a SpringRestGraphDatabase the operations that would + be expensive over the wire, like traversals and querying are executed efficiently on the server side by using the REST API to forward - those calls. All the other template methods require single network operations. + those calls. All the other template methods require individual network operations. + + The REST-batch-mode of the SpringRestGraphDatabase is not yet exposed via the template, but it is available + via the graph database. +
diff --git a/src/docbkx/reference/programming-model/transactions.xml b/src/docbkx/reference/programming-model/transactions.xml index d38081147..c8980ebc4 100644 --- a/src/docbkx/reference/programming-model/transactions.xml +++ b/src/docbkx/reference/programming-model/transactions.xml @@ -4,14 +4,24 @@ Transactions Neo4j is a transactional database, only allowing modifications to be performed within transaction - boundaries. Reading data does however not require transactions. + boundaries. Reading data does however not require transactions. Spring Data Neo4j integrates nicely + with both the declarative transaction support with @Transactional as well as the + manual transaction handling with TransactionTemplate. It also supports the rollback + mechanisms of the Spring Testing library. Spring Data Neo4j integrates with transaction managers configured using Spring. The simplest - scenario of just running the graph database uses a SpringTransactionManager provided by the - Neo4j kernel to be used with Spring's JtaTransactionManager. That is, configuring Spring to + scenario of just running the graph database uses a SpringTransactionManager provided by the + Neo4j kernel to be used with Spring's JtaTransactionManager. That is, configuring Spring to use Neo4j's transaction manager. + + + To avoid name collisons the transaction manager configured by Spring Data Neo4j is called neo4jTransactionManager + and is aliased to transactionManager. So defining a separate transactionManager bean should not + interfere with Spring Data Neo4j operations. + + The explicit XML configuration given below is encoded in the Neo4jConfiguration @@ -22,7 +32,8 @@ Simple transaction manager configuration - + @@ -35,12 +46,12 @@ - + ]]> For scenarios with multiple transactional resources there are two options. The first option - is to have Neo4j participate in the externally configured transaction manager by using the + is to have Neo4j participate in the externally configured transaction manager using the Spring support in Neo4j by enabling the configuration parameter for your graph database. Neo4j will then use Spring's transaction manager instead of its own. @@ -49,13 +60,15 @@ - + - + @@ -69,7 +82,7 @@
One can also configure a stock XA transaction manager (e.g. Atomikos, JOTM, App-Server-TM) to be - used with Neo4j and the other resources. For a bit less secure but fast 1 phase commit best effort, + used with Neo4j and the other resources. For a bit less secure but fast 1-phase-commit-best-effort, use ChainedTransactionManager, which comes bundled with Spring Data Neo4j. It takes a list of transaction managers as constructor params and will handle them in order for transaction start and commit (or rollback) in the reverse order. diff --git a/src/docbkx/reference/programming-model/typerepresentationstrategy.xml b/src/docbkx/reference/programming-model/typerepresentationstrategy.xml index 22eaa80a6..e4e388f41 100644 --- a/src/docbkx/reference/programming-model/typerepresentationstrategy.xml +++ b/src/docbkx/reference/programming-model/typerepresentationstrategy.xml @@ -8,16 +8,16 @@ this type information is saved in the graph database. - Implementations of TypeRepresentationStrategy take care of persisting this information on entity instance + Implementations of TypeRepresentationStrategy take care of persisting this information during entity instance creation. They also provide the repository methods that use this type information to perform their operations, - like findAll and count. + like findAll and count. The derived finderMethods also use the type information for graph global queries. There are three available implementations for node entities to choose from. - IndexingNodeTypeRepresentationStrategy + IndexingNodeTypeRepresentationStrategy this is the default strategy used. Stores entity types in the integrated index. Each entity node gets indexed with its type and @@ -25,7 +25,7 @@ is called__types__. Additionally, in order to get the type of an entity node, each node has a property __type__ - with the type of that entity. + with the fully qualified type of that entity. diff --git a/src/docbkx/reference/samples.xml b/src/docbkx/reference/samples.xml index 04f318781..07489a7f2 100644 --- a/src/docbkx/reference/samples.xml +++ b/src/docbkx/reference/samples.xml @@ -24,6 +24,10 @@ The unit tests demonstrate some other features of Spring Data Neo4j as well. The sample comes with a minimal configuration for Maven and Spring to get up and running quickly. + + The Hello Worlds application is available both for the simple mapping (hello-worlds) + and for the advanced mapping (hello-world-aspects). + Executing the application creates the following graph in the graph database: @@ -42,7 +46,7 @@ roles in different movies. It also uses graph traversal operations to calculate the Bacon number of any given actor. This sample application shows the usage of Spring Data Neo4j in a more complex setting, using several - annotated entities and relationships as well as indexes and graph traversals. + annotated entities and relationships as well as indexes and in-graph indexes and graph traversals.
See the readme file for instructions on how to compile and run the application. @@ -98,5 +102,33 @@
+
+ Cineasts social movie database + + The cineasts.net application was introduced extensively in the first part of this guide, the + tutorial. The tutorial covers the development of the simple mapping version of cineasts. + + + To document the differences, versions for the advanced mapping (cineasts-aspects) + and accessing the remote server (cineasts-rest) are also available. + + + A online version of cineasts can be found on cineasts.net. + A sample dataset of the cineasts databse is available at the neo4j sample-data page. + + + This is a subset of the visualization of the cineasts graph for the "Matrix" movie. + + + + + + + + + + + +
diff --git a/src/docbkx/reference/setup.xml b/src/docbkx/reference/setup.xml index 5c122cd34..f0104b493 100644 --- a/src/docbkx/reference/setup.xml +++ b/src/docbkx/reference/setup.xml @@ -17,15 +17,15 @@
Dependencies for Spring Data Neo4j Simple Mapping - For the POJO mapping it is enough to add the org.springframework.data:spring-data-neo4j:2.0.0.RC1 dependency - to your project. If you want to use the Cypher query language please add org.neo4j:neo4j-cypher:1.5 + For the simple POJO mapping it is enough to add the org.springframework.data:spring-data-neo4j:2.0.0.RELEASE dependency + to your project. If you want to use the Cypher query language please add org.neo4j:neo4j-cypher:1.6.M02 Maven dependencies for Spring Data Neo4j org.springframework.data spring-data-neo4j -2.0.0.RC1 +2.0.0.RELEASE ]]> @@ -43,8 +43,8 @@ org.springframework.data spring-data-neo4j-aspects - 2.0.0.RC1 + 2.0.0.RELEASE @@ -139,7 +139,7 @@ repositories { [ org.neo4j neo4j-cypher - 1.5 + 1.6.M02 ] ]]> @@ -272,6 +272,29 @@ repositories { ]]>
+
+ Repository Configuration + + Spring Data Neo4j repositories are configured using the <neo4j:repositories> + element which defines the base-package (or packages) for the repositories. + A reference to an existing Neo4jTemplate bean reference can be passed in as well. + + + As Spring Data Neo4j repositories build upon the infrastructure provided by + + Spring Data Commons, + the configuration options for repositories described there work here as well. + + + XML configuration for repositories + + + + + ]]> + +
Java-based bean configuration @@ -279,7 +302,7 @@ repositories { - For those not familiar with Java-based bean metadata in Spring, we recommend that you + For those not familiar with Java-based bean configuration in Spring, we recommend that you read up on it first. The Spring documentation has a high-level introduction as well as @@ -316,7 +339,8 @@ repositories { - Additional beans can be configured to be included in the Neo4j-Configuration. ConversionService for custom conversions, + Additional beans can be configured to be included in the Neo4j-Configuration just by defining them in the Spring context. + ConversionService for custom conversions, Validators for bean validation, TypeRepresentationStrategyFactory for configuring the in graph type representation, IndexProviders for custom index handling (e.g. for multi-tenancy), Entity-Instantiators (with their config) to have more control over the creation of entity instances and much more.