Merge branch 'master' of github.com:SpringSource/spring-data-neo4j
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ public abstract class Neo4jConfiguration {
|
||||
|
||||
@Bean
|
||||
public TypeRepresentationStrategyFactory typeRepresentationStrategyFactory() throws Exception {
|
||||
return new TypeRepresentationStrategyFactory(graphDatabase());
|
||||
return new TypeRepresentationStrategyFactory(graphDatabase(), indexProvider());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("$");
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
BIN
src/docbkx/reference/attachdetach.key
Normal file
BIN
src/docbkx/reference/attachdetach.key
Normal file
Binary file not shown.
BIN
src/docbkx/reference/attachdetach.png
Normal file
BIN
src/docbkx/reference/attachdetach.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
16
src/docbkx/reference/programming-model/conversion.xml
Normal file
16
src/docbkx/reference/programming-model/conversion.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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><T> Iterable<T> NodeBacked.findAllByQuery(final String query, final Class<T> targetType)</code></para>
|
||||
</listitem>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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<T> findAll()</code>
|
||||
(supported in future versions:
|
||||
<code>Iterable<T> findAll(Sort)</code> and
|
||||
<code>Page<T> findAll(Pageable)</code>)</para></listitem>
|
||||
<term>Iterate over all entities instances of the repository entity type</term>
|
||||
<listitem>
|
||||
<para>
|
||||
<code>Iterable<T> findAll()</code>
|
||||
<code>Iterable<T> findAll(Sort)</code>
|
||||
<code>Page<T> 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<T> save(Iterable<T>)</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<T>)</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)<-[: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)<-[: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<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<T> query(Execute query, params, Pageable page)</code>
|
||||
and the <code>EndResult<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><context:component-scan></code> in your spring config, please make sure to put it behind
|
||||
<code><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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<FROM,TO></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<FROM></code> is an
|
||||
<code>Iterable<TO></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<T></code>.
|
||||
<code>Iterable<TO></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<T></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<Path></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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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><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.
|
||||
|
||||
Reference in New Issue
Block a user