Merge branch 'master' of github.com:SpringSource/spring-data-neo4j

This commit is contained in:
Andreas Kollegger
2011-12-20 11:21:51 -05:00
35 changed files with 682 additions and 230 deletions

View File

@@ -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<Person> actors;
Set<Actor> actors;
@RelatedToVia(type = "ACTS_IN", direction = INCOMING)
Iterable<Role> roles;
@@ -57,7 +57,7 @@ public class Movie {
this.title = title;
}
public Collection<Person> getActors() {
public Collection<Actor> getActors() {
return actors;
}
@@ -106,7 +106,7 @@ public class Movie {
return allRatings == null ? Collections.<Rating>emptyList() : IteratorUtil.asCollection(allRatings);
}
public Person getDirector() {
public Director getDirector() {
return director;
}

View File

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

View File

@@ -141,7 +141,7 @@ public abstract class Neo4jConfiguration {
@Bean
public TypeRepresentationStrategyFactory typeRepresentationStrategyFactory() throws Exception {
return new TypeRepresentationStrategyFactory(graphDatabase());
return new TypeRepresentationStrategyFactory(graphDatabase(), indexProvider());
}
@Bean

View File

@@ -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

View File

@@ -54,8 +54,25 @@ public interface Neo4jPersistentProperty extends PersistentProperty<Neo4jPersist
boolean isSerializablePropertyField(final ConversionService conversionService);
/**
* Returns {@code true} if the type of this property is a natively supported neo4j property type. Supported type are listed here:
* {@link PropertyContainer#setProperty(String, Object)}.
*
* @return {@code true} if the given object is a natively supported neo4j property type.
* @see PropertyContainer#setProperty(String, Object)
*/
boolean isNeo4jPropertyType();
/**
* Returns {@code true} if the given object {@code value} is a natively supported neo4j property type, but not an array. Supported type are listed here:
* {@link PropertyContainer#setProperty(String, Object)}.
*
* @param value the object to check
* @return {@code true} if the given object is a natively supported neo4j property type. {@code false} is returned, if {@code value} is an array.
* @see PropertyContainer#setProperty(String, Object)
*/
boolean isNeo4jPropertyValue(Object value);
boolean isSyntheticField();
Collection<? extends Annotation> getAnnotations();

View File

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

View File

@@ -21,25 +21,41 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
public interface IndexProvider {
public abstract <S extends PropertyContainer, T> Index<S> getIndex(Class<T> type);
<S extends PropertyContainer, T> Index<S> getIndex(Class<T> type);
public abstract <S extends PropertyContainer, T> Index<S> getIndex(Class<T> type, String indexName);
<S extends PropertyContainer, T> Index<S> getIndex(Class<T> type, String indexName);
@SuppressWarnings("unchecked")
public abstract <S extends PropertyContainer, T> Index<S> getIndex(Class<T> type, String indexName,
<S extends PropertyContainer, T> Index<S> getIndex(Class<T> type, String indexName,
IndexType indexType);
@SuppressWarnings("unchecked")
public abstract <T extends PropertyContainer> Index<T> getIndex(String indexName);
<T extends PropertyContainer> Index<T> getIndex(String indexName);
public abstract boolean isNode(Class<? extends PropertyContainer> type);
boolean isNode(Class<? extends PropertyContainer> type);
// TODO handle existing indexes
@SuppressWarnings("unchecked")
public abstract <T extends PropertyContainer> Index<T> createIndex(Class<T> type, String indexName,
<T extends PropertyContainer> Index<T> createIndex(Class<T> type, String indexName,
IndexType fullText);
public abstract <S extends PropertyContainer> Index<S> getIndex(Neo4jPersistentProperty property,
<S extends PropertyContainer> Index<S> 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);
}

View File

@@ -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<S>) graphDatabase.getIndex(indexName);
if (persistentEntity.isRelationshipEntity()) return (Index<S>) graphDatabase.getIndex(indexName);
if (persistentEntity.isNodeEntity() || persistentEntity.isRelationshipEntity()) return (Index<S>) graphDatabase.getIndex(indexName);
throw new IllegalArgumentException("Wrong index type supplied: " + type + " expected Node- or Relationship-Entity");
}
if (persistentEntity.isNodeEntity()) return (Index<S>) createIndex(Node.class, indexName, indexType);
if (persistentEntity.isNodeEntity())
return (Index<S>) createIndex(Node.class, indexName, indexType);
if (persistentEntity.isRelationshipEntity())
return (Index<S>) 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;
}
}

View File

@@ -196,6 +196,14 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
|| (fieldType.isArray() && !fieldType.getComponentType().isArray() && isNeo4jPropertyType(fieldType.getComponentType()));
}
@Override
public boolean isNeo4jPropertyValue(Object value) {
if (value == null || value.getClass().isArray()) {
return false;
}
return isNeo4jPropertyType(value.getClass());
}
public boolean isSyntheticField() {
return getName().contains("$");
}

View File

@@ -24,6 +24,7 @@ import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
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 IndexingNodeTypeRepresentationStrategy implements NodeTypeRepresentationStrategy {
@@ -31,32 +32,39 @@ public class IndexingNodeTypeRepresentationStrategy implements NodeTypeRepresent
public static final String INDEX_NAME = "__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 IndexingNodeTypeRepresentationStrategy(GraphDatabase graphDb) {
public IndexingNodeTypeRepresentationStrategy(GraphDatabase graphDb, IndexProvider indexProvider) {
this.graphDb = graphDb;
this.indexProvider = indexProvider;
typeCache = new EntityTypeCache();
}
private Index<Node> getNodeTypesIndex() {
return graphDb.createIndex(Node.class,INDEX_NAME, IndexType.SIMPLE);
}
private Index<Node> 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 <U> ClosableIterable<Node> findAll(Class<U> clazz) {
@@ -64,9 +72,13 @@ public class IndexingNodeTypeRepresentationStrategy implements NodeTypeRepresent
}
private <Object> ClosableIterable<Node> findAllNodeBacked(Class<Object> clazz) {
final IndexHits<Node> allEntitiesOfType = getNodeTypesIndex().get(INDEX_KEY, clazz.getName());
String value = clazz.getName();
if (indexProvider != null)
value = indexProvider.createIndexValueForType(clazz);
final IndexHits<Node> allEntitiesOfType = getNodeTypesIndex().get(INDEX_KEY, value);
return new ClosableIndexHits<Node>(allEntitiesOfType);
}
}
@Override
public long count(Class<?> entityClass) {

View File

@@ -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 <U> ClosableIterable<Relationship> findAll(Class<U> clazz) {
@@ -63,7 +71,11 @@ public class IndexingRelationshipTypeRepresentationStrategy implements Relations
}
private <Object> ClosableIterable<Relationship> findAllRelBacked(Class<Object> clazz) {
final IndexHits<Relationship> allEntitiesOfType = getRelTypesIndex().get(INDEX_KEY, clazz.getName());
String value = clazz.getName();
if (indexProvider != null)
value = indexProvider.createIndexValueForType(clazz);
final IndexHits<Relationship> allEntitiesOfType = getRelTypesIndex().get(INDEX_KEY, value);
return new ClosableIndexHits<Relationship>(allEntitiesOfType);
}

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<chapter id="reference:aspectj-details">
<title>AspectJ details</title>
<para>
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 <ulink url="https://secure.wikimedia.org/wikipedia/en/wiki/Aspect-oriented_programming">aspect-oriented
programming</ulink> 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,

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@@ -3,7 +3,8 @@
<chapter id="reference:cross-store">
<title>Cross-store persistence</title>
<para>
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.
</para>
@@ -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 <code>persist()</code>.
database on the persist operation.
</para>
<para>
The association between the two entities is maintained via a FOREIGN_ID field in the node, that

View File

@@ -160,7 +160,8 @@ for (Node foundNode : nodeIndex.get("property","value")) {
<ulink url="http://docs.neo4j.org/chunked/milestone/cypher-query-lang.html">"Cypher"</ulink> 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
<ulink url="http://video.neo4j.org/ybMbf/screencast-introduction-to-cypher/">Neo4j video site</ulink>.
</para>
<para>
Cypher queries always begin with a <code>start</code> set of nodes. Those can be either expressed by their

View File

@@ -8,7 +8,7 @@
of using Spring Data Neo4j instead of the Neo4j API directly.
</para>
<section>
<title>When is Spring Data Neo4j right</title>
<title>When to use Spring Data Neo4j</title>
<para>
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.
</para>
<para>
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.
</para>
<para>
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).
</para>
to pure graph operations.
</para>
<para>
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.
</para>
<para>
For the <emphasis>simple mapping</emphasis> 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 (<code>@Fetch</code>) to do so. Alternatively
the <code>Neo4jTemplate.fetch</code> method offers means of of loading entities and collections of those.
</para>
<para>
For the <emphasis>advanced mapping mode</emphasis> 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).
</para>
<para>
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.
</para>
</section>
</chapter>

View File

@@ -1,24 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<section id="reference:programming-model:lifecycle">
<title>Detached node entities</title>
<title>Detached node entities in advanced mapping mode</title>
<para>
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.
</para>
<para>
Node entities can be in two different persistence states: attached or detached. By default, newly created node
entities are in the detached state. When <code>persist()</code> is called on the entity, it becomes
entities are in the detached state. When <code>persist() or template.save()</code> is called on the entity, it becomes
attached to the graph, and its properties and relationships are stores in the database. If
<code>persist()</code> 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.
</para>
<para>
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 <code>persist()</code>.
changes are stored in the entity (its fields) itself until the next call to a save operation.
</para>
<para>
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 <code>persist()</code> for the data to be saved.
</para>
<mediaobject>
<imageobject>
<imagedata fileref="attachdetach.png" scalefit="1" contentwidth="15cm"/>
</imageobject>
</mediaobject>
<!--<para>-->
<!--Persisting an entity not only persists that single entity but will traverse its existing and new relationships-->
<!--and persist the cluster of detached entities that it is part of. The borders of this cluster are formed by-->
@@ -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.
</para>
<para>
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

View File

@@ -3,11 +3,12 @@
<section id="reference:programming-model:validation">
<title>Bean validation (JSR-303)</title>
<para>
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. <code>@Min</code>, <code>@Max</code>,
<code>@Size</code>, etc. Validation errors throw a <code>ValidationException</code>. 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 <code>GraphDatabaseContext</code>.
has to be registered with the <code>Neo4jTemplate</code>, which is done automatically by the <code>Neo4jConfiguration</code>
if one is present in the Spring Config.
</para>
<example>
<title>Bean validation</title>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<section id="reference:programming-model:conversion">
<title>Conversion</title>
<para>
<code>Neo4jTemplate</code> 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.
</para>
<para>
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.
</para>
<para>
It is also possible to provide a custom ResultConverter that additionally takes care of conversions.
</para>
</section>

View File

@@ -3,10 +3,16 @@
<section id="reference:programming-model:indexing">
<title>Indexing</title>
<para>
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.
</para>
<para>
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.
</para>
<section>
@@ -23,20 +29,30 @@
other fields are indexed with their string representation.
</para>
<para>
The @Indexed annotation also provides the option of using a custom index. The default index
The <code>@Indexed</code> 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.
</para>
<para>
If a field is declared in a superclass but different indexes for subclasses are needed, the
<code>level</code> attribute declares what will be used as index. <code>Level.CLASS</code>
uses the class where the field was declared and <code>Level.INSTANCE</code> uses the class
that is provided or of the actual entity instance.
</para>
<para>
The indexes can be queried by using a repository (see
<xref linkend="reference:programming-model:repositories" />).
Typically, the repository is an instance of
<code>org.springframework.data.neo4j.repository.DirectGraphRepositoryFactory</code>.
The repository is an instance of
<code>org.springframework.data.neo4j.repository.IndexRepository</code>.
The methods <code>findByPropertyValue()</code> and <code>findAllByPropertyValue()</code> work on
the exact indexes and return the first or all matches. To do range queries, use
<code>findAllByRange()</code> (please note that currently both values are inclusive).
</para>
<para>
For providing explicit index names the repository has to extend <code>NamedIndexRepository</code>.
This adds the shown methods with another signature that take the index name as first parameter.
</para>
<example>
<title>Indexing entities</title>
<programlisting language="java"><![CDATA[@NodeEntity
@@ -63,7 +79,7 @@ for (Person middleAgedDeveloper : graphRepository.findAllByRange("age", 20, 40))
<para>
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
<code>@Indexed</code> annotation has the boolean <code>fulltext</code> attribute.
<code>@Indexed</code> annotation has the <code>type</code> attribute which can be set to <code>IndexType.FULLTEXT</code>.
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 <code>findAllByQuery()</code> repository method.
Wildcards like <code>*</code> are allowed. Generally though, the fulltext querying rules of the
underlying index provider apply. See the
<ulink url="http://lucene.apache.org/java/3_0_1/">Lucene documentation</ulink> for more
<ulink url="http://lucene.apache.org">Lucene documentation</ulink> for more
information on this.
</para>
<para>
@@ -101,41 +117,73 @@ Person mark = graphRepository.findAllByQuery("people-search", "name", "ma*");
<section>
<title>Manual index access</title>
<para>
The index for a domain class is also available from <code>GraphDatabaseContext</code> via
The index for a domain class is also available from <code>Neo4jTemplate</code> via
the <code>getIndex()</code> 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.
</para>
<example>
<title>Manual index usage</title>
<programlisting language="java"><![CDATA[@Autowired GraphDatabaseContext gdc;
<title>Manual index retrieval by type and name</title>
<programlisting language="java"><![CDATA[@Autowired Neo4jTemplate template;
// Default index
Index<Node> personIndex = gdc.getIndex(Person.class);
Index<Node> 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<Node> namedPersonIndex = gdc.getIndex(Person.class, "people");
Index<Node> namedPersonIndex = template.getIndex("people",Person.class);
namedPersonIndex.get("name", "Mark");
// Fulltext index
Index<Node> personFulltextIndex = gdc.getIndex(Person.class, "people-search", true);
Index<Node> personFulltextIndex = template.getIndex("people-search", Person.class);
personFulltextIndex.query("name", "*cha*");
personFulltextIndex.query("{name:*cha*}");
]]></programlisting>
</example>
<para>
It is also possible to pass in the property name of the entity with an <code>@Indexed</code> annotation whose
index should be returned.
</para>
<example>
<title>Manual index retrieval by property configuration</title>
<programlisting language="java"><![CDATA[@Autowired Neo4jTemplate template;
Index<Node> 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<Node> personFulltextIndex = template.getIndex(Person.class,"name");
personFulltextIndex.query("name", "*cha*");
personFulltextIndex.query("{name:*cha*}");
]]></programlisting>
</example>
</section>
<section>
<title>Indexing in Neo4jTemplate</title>
<para>
Neo4jTemplate also offers index support, providing auto-indexing for fields at creation time.
There is an <code>autoIndex</code> method that can also add indexes for a set of fields in one go.
</para>
<title>Index queries in Neo4jTemplate</title>
<para>
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 <code>PathMapper</code> to be converted or collected.
parameters or a query object/expression, return the results as <code>Result</code> objects which
then can be converted and projected further using the result-conversion-dsl (see <xref linkend="reference:template"/>).
</para>
</section>
<section>
<title>Neo4j Auto Indexes</title>
<para>
Neo4j allows to configure <ulink url="http://docs.neo4j.org/chunked/milestone/auto-indexing.html">auto-indexing</ulink>
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 <code>node_auto_index</code> and <code>relationship_auto_index</code> when
querying indexes in Spring Data Neo4j either with the query methods in template and repositories or via Cypher and Gremlin.
</para>
</section>
<section>
<title>Spatial Indexes</title>
<para>
Spring Data Neo4j offers limited support for spatial queries using the <code>neo4j-spatial</code> library. See the
separate chapter <xref linkend="reference:spatial"/> for details.
</para>
</section>
</section>

View File

@@ -1,8 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<section id="reference:programming-model:introduced-methods">
<title>Introduced methods</title>
<title>Active Record Methods for Advanced Mapping Mode</title>
<para>
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.
</para>
<para>
The node and relationship aspects introduce (via AspectJ ITD - inter type declaration) several
methods to the entities.
<variablelist>
@@ -114,7 +120,7 @@
</listitem>
</varlistentry>
<varlistentry>
<term>Executes the given query, providing the <code>{self}</code> variable with the node-id and returning the results converted to the target type.</term>
<term>Executes the given Cypher query, providing the <code>{self}</code> variable with the node-id and returning the results converted to the target type.</term>
<listitem>
<para><code>&lt;T&gt; Iterable&lt;T&gt; NodeBacked.findAllByQuery(final String query, final Class&lt;T&gt; targetType)</code></para>
</listitem>

View File

@@ -39,6 +39,26 @@ public class Movie {
</example>
</section>
<section>
<title>@GraphId: Neo4j -id field</title>
<para>
For the simple mapping this is a required field which must be of type <code>Long</code>. It is used
by Spring Data Neo4j to store the node or relationship-id to re-connect the entity to the graph.
</para>
<note>
<para>
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 <code>equals()</code> and <code>hashCode()</code> method have to be provided which take the <code>id</code>
field into account (and also handle the "non-attached", null case).
</para>
</note>
<para>
For the advanced mapping such a field is optional. Only if the underlying id has to be accessed, it is
needed.
</para>
</section>
<section>
<title>@GraphProperty: Optional annotation for property fields</title>
<para>

View File

@@ -16,8 +16,8 @@
context and only offers the attributes and methods needed here would be very beneficial.
</para>
<para>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.
</para>
<para>
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
}
]]></programlisting>
</example>
</section>

View File

@@ -4,37 +4,32 @@
<title>CRUD with repositories</title>
<para>
The repositories provided by Spring Data Neo4j build on the composable repository infrastructure
in <ulink url="http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations">Spring Data Commons</ulink>.
in <ulink url="http://static.springsource.org/spring-data/data-commons/docs/current/reference/html/#repositories">Spring Data Commons</ulink>.
They allow for interface based composition of repositories consisting of provided default
implementations for certain interfaces and additional custom implementations for other methods.
</para>
<!--<note>-->
<!--<para>-->
<!--Spring Data Neo4j provides only the infrastructure and some default repository implementations-->
<!--so far. Future releases will support finders derived from method names, named queries, and-->
<!--annotated query methods.-->
<!--(e.g.-->
<!--<code>findByName(name)</code>,-->
<!--<code>@Query(name="find-by-name-query") findByName(name)</code>, and-->
<!--<code>@Query(query="{name:%s}") findByName(name)</code>)-->
<!--</para>-->
<!--</note>-->
<para>
Spring Data Neo4j repositories support annotated and named queries for the Neo4j
<ulink url="http://docs.neo4j.org/chunked/milestone/query-lang.html">Cypher</ulink> query-language.
<ulink url="http://docs.neo4j.org/chunked/milestone/query-lang.html">Cypher</ulink> query-language and
<code>Gremlin</code> graph DSL.
</para>
<para>
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. <code>CRUDRepository</code> provides basic operations,
<code>IndexRepository</code> and <code>NamedIndexRepository</code> delegate to Neo4j's internal
indexing subsystem for queries, and <code>TraversalRepository</code> handles Neo4j traversals.
</para>
<para>
<code>GraphRepository</code> is a convenience repository interface, extending <code>CRUDRepository</code>,
With the <code>RelationshipOperationsRepository</code> it is possible to access, create and delete
relationships between entitites or nodes.
The <code>SpatialRepository</code> allows geographic searches (<xref linkend="reference:spatial"/>)
</para>
<para>
<code>GraphRepository</code> is a convenience repository interface, combining <code>CRUDRepository</code>,
<code>IndexRepository</code>, and <code>TraversalRepository</code>. Generally, it has all the
desired repository methods. If named index operations are required, then <code>NamedIndexRepository</code>
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.
</para>
<section>
@@ -45,41 +40,38 @@
for type based queries.
<variablelist>
<varlistentry>
<term>Load an instance via a Neo4j node id</term>
<term>Load an entity instance via an id</term>
<listitem><para><code>T findOne(id)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Check for existence of a Neo4j node id</term>
<term>Check for existence of a id in the graph</term>
<listitem><para><code>boolean exists(id)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Iterate over all nodes of a node entity type</term>
<listitem><para><code>Iterable&lt;T&gt; findAll()</code>
(supported in future versions:
<code>Iterable&lt;T&gt; findAll(Sort)</code> and
<code>Page&lt;T&gt; findAll(Pageable)</code>)</para></listitem>
<term>Iterate over all entities instances of the repository entity type</term>
<listitem>
<para>
<code>Iterable&lt;T&gt; findAll()</code>
<code>Iterable&lt;T&gt; findAll(Sort)</code>
<code>Page&lt;T&gt; findAll(Pageable)</code>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Count the instances of a node entity type</term>
<term>Count the instances of the repository entity type</term>
<listitem><para><code>Long count()</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Save a graph entity</term>
<term>Save entities</term>
<listitem><para><code>T save(T)</code> and <code>Iterable&lt;T&gt; save(Iterable&lt;T&gt;)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Delete a graph entity</term>
<term>Delete graph entities</term>
<listitem><para><code>void delete(T)</code>, <code>void; delete(Iterable&lt;T&gt;)</code>,
and <code>deleteAll()</code></para></listitem>
</varlistentry>
</variablelist>
</para>
<para>
Important to note here is that the <code>save</code>, <code>delete</code>, and <code>deleteAll</code>
methods are only there to conform to the <code>org.springframework.data.repository.Repository</code>
interface. The recommended way of saving and deleting entities is by using <code>entity.persist()</code>
and <code>entity.remove()</code>.
</para>
</section>
<section>
@@ -128,30 +120,33 @@
</section>
<section>
<title>Cypher queries</title>
<title>Query and Finder Methods</title>
<section>
<title>Annotated queries</title>
<para>
Queries using the Cypher graph query language can be supplied with the <code>@Query</code> annotation.
That means every method annotated with <code>@Query("start n=(%node) match (n)-->(m) return m")</code>
will use the supplied query string. The named parameter <code>%node</code> 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 <code>@Query("start n=node:IndexName(key={node or 0}) match (n)-->(m) return m")</code>
will use the supplied query string. The named or indexed parameter <code>{node}</code> 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 <code>Sort</code> and <code>Pageable</code>
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
<code>@Param("node")</code> annotation or enable debug symbols.
<code>@Param("node")</code> annotation or enable debug symbols. Indexed parameters are always usable.
</para>
<para>
Gremlin queries can be used similarly, the <code>@Query</code> annotation would just need a <code>type=QueryType.GREMLIN</code> attribute.
Parameters are supported in the same way.
</para>
</section>
<section>
<title>Named queries</title>
<para>Spring Data Neo4j also supports the notion of named queries which are externalized in property-config-files
(<code>META-INF/neo4j-named-queries.properties</code>). Those files have the format:
<code>Entity.finderName=query</code> (e.g. <code>Person.findBoss=start p=({p_person}) match (p)&lt;-[:BOSS]-(boss) return boss</code>).
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 <code>@Param("p_person")</code> annotation or enable debug symbols.
<code>Entity.finderName=query</code> (e.g. <code>Person.findBoss=start p=node({0}) match (p)&lt;-[:BOSS]-(boss) return boss</code>).
Otherwise named queries support the same parameters as annotated queries.
</para>
</section>
<section>
@@ -163,7 +158,7 @@
</section>
<section>
<title>Cypher examples</title>
<para>There is a <ulink url="http://neo4j.vidcaster.com/U2Y/introduction-to-cypher">screencast</ulink> available showing many features of the query language.
<para>There is a <ulink url="http://video.neo4j.org/ybMbf/screencast-introduction-to-cypher">screencast</ulink> available showing many features of the query language.
The following examples are taken from the cineasts dataset of the tutorial section.
<variablelist>
<varlistentry>
@@ -189,36 +184,118 @@
</variablelist>
</para>
</section>
<section>
<title>Queries derived from finder-method names</title>
<para>As known from Rails or Grails it is possible to derive queries for domain entities from finder method names
like <code>Iterable&lt;Person> findByNameAndAgeGreaterThan(String name, int age)</code>.
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.
<code>@Indexed</code> fields will be converted into index-lookups of the <code>start</code> clause,
navigation along relationships will be reflected in the <code>match</code> clause properties with operators will end up as expressions in the
<code>where</code> clause. Order and limiting of the query will by handled by provided <code>Pageable</code> or <Sort></Sort> 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.
</para>
<para>
<example>
<title>Some examples of methods and resulting cypher queries of a PersonRepository</title>
<programlisting language="java"><![CDATA[
public interface PersonRepository
extends GraphRepository<Person> {
// 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<Person> findByNameLike(String name)
// start person=node:__types__("className"="com...Person")
// where person.age = {0} and person.married = {1}
// return person
Iterable<Person> 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<Person> findByParentAgeAndMarried(int age, boolean married)
}
]]></programlisting>
</example>
</para>
</section>
<xi:include href="../../snippets/SnippetRepositoryDerivedFinder.xml"/>
</section>
<section>
<!-- todo Rickard -->
<title>CypherDSL repository</title>
<para>
Spring Data Neo4j supports the new cypher-dsl to write Cypher queries in a statically typed way. Just by including
<code>CypherDslRepository</code> to your repository you get the <code>Page&lt;T> query(Execute query, params, Pageable page)</code>
and the <code>EndResult&lt;T> query(Execute query, params);</code>. The result type of the Cypher-DSL builder is called
<code>Execute</code>
</para>
<example>
<title>Examples for Cypher-DSL repository</title>
<!-- TODO QueryDSL -->
<programlisting language="java"><![CDATA[
public interface PersonRepository extends GraphRepository<Person>,
CypherDslRepository<Person> {}
@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<Person> 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<Person> people = repo.query(query , map("name","Neo4j"));
]]></programlisting>
</example>
</section>
<!-- todo spatial repositories, cypherdsl-repositories -->
<section>
<title>Creating repositories</title>
<para>
The <code>Repository</code> instances are either created manually via a
<code>DirectGraphRepositoryFactory</code>, bound to a concrete node or relationship entity class.
The <code>DirectGraphRepositoryFactory</code> is configured in the Spring context and can be injected.
The <code>Repository</code> instances should normally be injected but can also be created manually via the
<code>Neo4jTemplate</code>.
</para>
<example>
<title>Using GraphRepositories</title>
<programlisting language="java"><![CDATA[GraphRepository<Person> graphRepository = template
.repositoryFor(Person.class);
<title>Using basic GraphRepository methods</title>
<programlisting language="java"><![CDATA[
public interface PersonRepository extends GraphRepository<Person> {}
Person michael = graphRepository.save(new Person("Michael", 36));
@Autowired PersonRepository repo;
// OR
GraphRepository<Person> 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<Person> devs = graphRepository.findAllByProperyValue("occupation", "developer");
Person mark = repo.findByPropertyValue("name", "mark");
Iterable<Person> middleAgedPeople = graphRepository.findAllByRange("age", 20, 40);
Iterable<Person> devs = repo.findAllByProperyValue("occupation", "developer");
Iterable<Person> aTeam = graphRepository.findAllByQuery("name", "A*");
Iterable<Person> middleAgedPeople = repo.findAllByRange("age", 20, 40);
Iterable<Person> davesFriends = graphRepository.findAllByTraversal(dave,
Iterable<Person> aTeam = repo.findAllByQuery("name", "A*");
Iterable<Person> davesFriends = repo.findAllByTraversal(dave,
Traversal.description().pruneAfterDepth(1)
.relationships(KNOWS).filter(returnAllButStartNode()));
]]></programlisting>
@@ -237,6 +314,29 @@ Iterable<Person> davesFriends = graphRepository.findAllByTraversal(dave,
<title>Composing repositories</title>
<programlisting language="java"><![CDATA[public interface PersonRepository extends GraphRepository<Person>, PersonRepositoryExtension {}
// configure the repositories, preferably via the neo4j:repositories namespace
// (template reference is optional)
<neo4j:repositories base-package="org.example.repository"
graph-database-context-ref="template"/>
// 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<Person> devs = personRepository.findAllByPropertyValue("occupation","developer");
Iterable<Person> aTeam = graphRepository.findAllByQuery( "name","A*");
Iterable<Person> friends = personRepository.findFriends(dave);
// alternatively select some of the required repositories individually
public interface PersonRepository extends CRUDGraphRepository<Node,Person>,
IndexQueryExecutor<Node,Person>, TraversalQueryExecutor<Node,Person>,
@@ -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)
<neo4j:repositories base-package="org.springframework.data.neo4j"
graph-database-context-ref="template"/>
// have it injected
@Autowired
PersonRepository personRepository;
Person michael = personRepository.save(new Person("Michael",36));
Person dave=personRepository.findOne(123);
Iterable<Person> devs = personRepository.findAllByPropertyValue("occupation","developer");
Iterable<Person> aTeam = graphRepository.findAllByQuery( "name","A*");
Iterable<Person> friends = personRepository.findFriends(dave);
]]></programlisting>
</example>
<note>
<para>
If you use <code>&lt;context:component-scan></code> in your spring config, please make sure to put it behind
<code>&lt;neo4j:repositories></code>, as the RepositoryFactoryBean adds new bean definitions for all the declared
repositories, the context scan doesn't pick them up otherwise.
</para>
</note>
</section>
</section>

View File

@@ -30,6 +30,13 @@
for classes. Both <code>Neo4jPersistentEntitity</code> as well as <code>Neo4jPersistentProperty</code> provide access to that
information on their scope.
</para>
<note>
<para>
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.
</para>
</note>
<para>
<example>
<title>Examples for loading entities from the graph</title>

View File

@@ -4,7 +4,7 @@
<title>Geospatial Queries</title>
<para>
<code>SpatialRepository</code> is a dedicated Repository for spatial queries.
Spring Data Neo4j provides an optional dependency to <code>neo4j-spatial</code> which is an advanced library
Spring Data Neo4j provides an optional dependency to <ulink url="https://github.com/neo4j/spatial">neo4j-spatial</ulink> which is an advanced library
for GIS operations. So if you include the maven dependency in your <code>pom.xml</code>, Neo4j-Spatial and
the required <code>SPATIAL</code> index provider is available.
</para>
@@ -28,12 +28,12 @@
</para>
<para>
<example>
<title>Fields of </title>
<title>Fields of Well Known Text</title>
<programlisting language="java"><![CDATA[
@NodeEntity
class Venue {
String name;
@Indexed(type = POINT, indexName = "...") String wkt;
@Indexed(type = POINT, indexName = "VenueLocation") String wkt;
public void setLocation(float lon, float lat) {
this.wkt = String.format("POINT( %.2f %.2f )",lon,lat);
}

View File

@@ -1,10 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<section id="reference:template" xmlns:xi="http://www.w3.org/2001/XInclude">
<title>Neo4jTemplate</title>
<!-- todo make sure that all template methods are covered in this chapter -->
<para>
The <code>Neo4jTemplate</code> 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 (<xref linkend="reference:mapping"/>) transparently.
</para>
<para>
Besides methods for creating, storing and deleting entities, nodes and relationships in the graph, <code>Neo4jTemplate</code>
also offers a wide range of query methods. To reduce the proliferation of query methods a simple result handling
DSL was added.
</para>
<xi:include href="../../snippets/SnippetNeo4jTemplateMethods.xml"/>
@@ -17,8 +26,9 @@
<code>ResultConverter&lt;FROM,TO&gt;</code> 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 <code>Result&lt;FROM&gt;</code> is an
<code>Iterable&lt;TO&gt;</code>. Results can be limited to a single value using the <code>result.single()</code>
method. It also offers support for a pure callback function using a <code>Handler&lt;T&gt;</code>.
<code>Iterable&lt;TO&gt;</code>. Results can be limited to a single value using the
<code>result.single() or result.singleOrNull()</code>
methods. It also offers support for a pure callback function using a <code>Handler&lt;T&gt;</code>.
</para>
</section>
<section>
@@ -37,7 +47,7 @@
<para>
The traversal methods are at the core of graph operations.
The <code>traverse()</code> method covers the full traversal operation that takes a
<code>TraversalDescription</code> (typically built with the <code>Traversal.description()</code>
<code>TraversalDescription</code> (typically built with the <code>template.getGraphDatabase().traversalDescription()</code>
DSL) and runs it from the given start node. <code>traverse</code> returns a <code>Result&lt;Path&gt;</code>
to be used or transformed.
</para>
@@ -62,18 +72,21 @@
<section>
<title>Transactions</title>
<para>
The <code>Neo4jTemplate</code> 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 <code>useExplicitTransactions</code> parameter set to
true, it won't create any transactions so you have to provide them using <code>@Transactional</code>
or the <code>TransactionTemplate</code>.
The <code>Neo4jTemplate</code> provides implicit transactions for some of its methods. For instance
<code>save</code> uses them. For other modifying operations please provide Spring Transaction management
using <code>@Transactional</code> or the <code>TransactionTemplate</code>.
</para>
</section>
<section>
<title>Neo4j REST Server</title>
<para>If the template is configured to use a <code>RestGraphDatabase</code> the expensive operations
<para>If the template is configured to use a <code>SpringRestGraphDatabase</code> 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.
</para>
<para>
The REST-batch-mode of the <code>SpringRestGraphDatabase</code> is not yet exposed via the template, but it is available
via the graph database.
</para>
</section>
</section>

View File

@@ -4,14 +4,24 @@
<title>Transactions</title>
<para>
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 <code>@Transactional</code> as well as the
manual transaction handling with <code>TransactionTemplate</code>. It also supports the rollback
mechanisms of the Spring Testing library.
</para>
<para>
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 <code>SpringTransactionManager</code> provided by the
Neo4j kernel to be used with Spring's <code>JtaTransactionManager</code>. That is, configuring Spring to
use Neo4j's transaction manager.
</para>
<note>
<para>
To avoid name collisons the transaction manager configured by Spring Data Neo4j is called <code>neo4jTransactionManager</code>
and is aliased to <code>transactionManager</code>. So defining a separate <code>transactionManager</code> bean should not
interfere with Spring Data Neo4j operations.
</para>
</note>
<note>
<para>
The explicit XML configuration given below is encoded in the <code>Neo4jConfiguration</code>
@@ -22,7 +32,8 @@
</note>
<example>
<title>Simple transaction manager configuration</title>
<programlisting language="xml"><![CDATA[<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<programlisting language="xml"><![CDATA[<bean id="neo4jTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<bean class="org.neo4j.kernel.impl.transaction.SpringTransactionManager">
<constructor-arg ref="graphDatabaseService"/>
@@ -35,12 +46,12 @@
</property>
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
<tx:annotation-driven mode="aspectj" transaction-manager="neo4jTransactionManager"/>
]]></programlisting>
</example>
<para>
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.
</para>
@@ -49,13 +60,15 @@
<programlisting language="xml"><![CDATA[<![CDATA[<context:annotation-config />
<context:spring-configured/>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<bean id="jotm" class="org.springframework.data.neo4j.transaction.JotmFactoryBean"/>
</property>
</bean>
<bean class="org.neo4j.kernel.EmbeddedGraphDatabase" destroy-method="shutdown">
<bean id="graphDatabaseService" class="org.neo4j.kernel.EmbeddedGraphDatabase"
destroy-method="shutdown">
<constructor-arg value="target/test-db"/>
<constructor-arg>
<map>
@@ -69,7 +82,7 @@
</example>
<para>
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 <code>ChainedTransactionManager</code>, 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.

View File

@@ -8,16 +8,16 @@
this type information is saved in the graph database.
</para>
<para>
Implementations of <code>TypeRepresentationStrategy</code> take care of persisting this information on entity instance
Implementations of <code>TypeRepresentationStrategy</code> 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 <code>findAll</code> and <code>count</code>. The derived finderMethods also use the type information for graph global queries.
</para>
<para>
There are three available implementations for node entities to choose from.
<itemizedlist>
<listitem>
<para>
<code>IndexingNodeTypeRepresentationStrategy</code>
<code>IndexingNodeTypeRepresentationStrategy</code> this is the default strategy used.
</para>
<para>
Stores entity types in the integrated index. Each entity node gets indexed with its type and
@@ -25,7 +25,7 @@
is called<code>__types__</code>. Additionally, in order to get the type of an entity node, each
node has a property
<code>__type__</code>
with the type of that entity.
with the fully qualified type of that entity.
</para>
</listitem>
<listitem>

View File

@@ -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.
</para>
<para>
The Hello Worlds application is available both for the simple mapping (<code>hello-worlds</code>)
and for the advanced mapping (<code>hello-world-aspects</code>).
</para>
<para>
Executing the application creates the following graph in the graph database:
</para>
@@ -42,7 +46,7 @@
roles in different movies. It also uses graph traversal operations to calculate the
<ulink url="http://en.wikipedia.org/wiki/Bacon_number">Bacon number</ulink> 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.
</para>
<para>
See the readme file for instructions on how to compile and run the application.
@@ -98,5 +102,33 @@
</imageobject>
</mediaobject>
</section>
<section id="samples:cineasts">
<title>Cineasts social movie database</title>
<para>
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.
</para>
<para>
To document the differences, versions for the advanced mapping (<code>cineasts-aspects</code>)
and accessing the remote server (<code>cineasts-rest</code>) are also available.
</para>
<para>
A online version of cineasts can be found on <ulink url="http://cineasts.net">cineasts.net</ulink>.
A sample dataset of the cineasts databse is available at the neo4j <ulink url="http://sample-data.neo4j.org">sample-data page</ulink>.
</para>
<para>
This is a subset of the visualization of the cineasts graph for the "Matrix" movie.
</para>
<mediaobject>
<imageobject>
<imagedata fileref="../tutorial/cineasts_graph.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="../tutorial/cineasts_main.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</section>
</chapter>

View File

@@ -17,15 +17,15 @@
<section>
<title>Dependencies for Spring Data Neo4j Simple Mapping</title>
<para>
For the POJO mapping it is enough to add the <code>org.springframework.data:spring-data-neo4j:2.0.0.RC1</code> dependency
to your project. If you want to use the Cypher query language please add <code>org.neo4j:neo4j-cypher:1.5</code>
For the simple POJO mapping it is enough to add the <code>org.springframework.data:spring-data-neo4j:2.0.0.RELEASE</code> dependency
to your project. If you want to use the Cypher query language please add <code>org.neo4j:neo4j-cypher:1.6.M02</code>
</para>
<example>
<title>Maven dependencies for Spring Data Neo4j</title>
<programlisting language="xml"><![CDATA[<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>2.0.0.RC1</version>
<version>2.0.0.RELEASE</version>
</dependency>
]]></programlisting>
</example>
@@ -43,8 +43,8 @@
<programlisting language="java"><![CDATA[sourceCompatibility = 1.6
targetCompatibility = 1.6
springVersion = "3.0.6.RELEASE"
springDataNeo4jVersion = "2.0.0.RC1"
springVersion = "3.0.7.RELEASE"
springDataNeo4jVersion = "2.0.0.RELEASE"
aspectjVersion = "1.6.12"
apply from:'https://github.com/SpringSource/spring-data-neo4j/raw/master/build/
@@ -128,7 +128,7 @@ repositories {
<programlisting language="xml"><![CDATA[<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-aspects</artifactId>
<version>2.0.0.RC1</version>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
@@ -139,7 +139,7 @@ repositories {
[<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-cypher</artifactId>
<version>1.5</version>
<version>1.6.M02</version>
</dependency>]
]]></programlisting>
</example>
@@ -272,6 +272,29 @@ repositories {
]]></programlisting>
</example>
</section>
<section>
<title>Repository Configuration</title>
<para>
Spring Data Neo4j repositories are configured using the <code>&lt;neo4j:repositories></code>
element which defines the base-package (or packages) for the repositories.
A reference to an existing <code>Neo4jTemplate</code> bean reference can be passed in as well.
</para>
<para>
As Spring Data Neo4j repositories build upon the infrastructure provided by
<ulink url="http://static.springsource.org/spring-data/data-commons/docs/current/reference/html/#repositories.create-instances">
Spring Data Commons</ulink>,
the configuration options for repositories described there work here as well.
</para>
<example>
<title>XML configuration for repositories</title>
<programlisting language="xml"><![CDATA[
<neo4j:repositories base-package="org.example.repository"/>
<!-- with template bean reference -->
<neo4j:repositories base-package="org.example.repository" graph-database-context-ref="template"/>
]]></programlisting>
</example>
</section>
<section>
<title>Java-based bean configuration</title>
<para>
@@ -279,7 +302,7 @@ repositories {
</para>
<note>
<para>
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
<ulink url="http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/new-in-3.0.html#new-java-configuration">high-level introduction</ulink>
as well as
@@ -316,7 +339,8 @@ repositories {
</para>
<para>
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.