Merge branch 'master' into snapshot

This commit is contained in:
Michael Hunger
2011-04-12 14:14:04 +02:00
18 changed files with 513 additions and 411 deletions

View File

@@ -62,6 +62,4 @@ public interface EntityState<ENTITY extends GraphBacked<STATE>,STATE> {
STATE getPersistentState();
ENTITY persist();
boolean refersTo(GraphBacked target);
}

View File

@@ -147,7 +147,4 @@ public interface NodeBacked extends GraphBacked<Node> {
* @return the newly created relationship to the target node
*/
Relationship relateTo(NodeBacked target, String type);
// will possibly be used for object graphs
boolean refersTo(GraphBacked target);
}

View File

@@ -1,59 +0,0 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.fieldaccess;
import org.springframework.data.graph.core.EntityState;
import org.springframework.data.graph.core.GraphBacked;
import org.springframework.data.graph.core.NodeBacked;
import java.util.*;
/**
* @author mh
* @since 12.03.11
*/
public class BackReferences {
private List<NodeBacked> backrefs=new ArrayList<NodeBacked>();
private EntityState<?, ?> entityState;
public BackReferences(EntityState<?,?> entityState) {
this.entityState = entityState;
}
public void addBackReferences(Collection<NodeBacked> backReference) {
this.backrefs.addAll(backReference);
}
private void pruneInvalidBackRefs() {
GraphBacked entity = entityState.getEntity();
for (Iterator<NodeBacked> it = backrefs.iterator(); it.hasNext();) {
NodeBacked backRef = it.next();
if (backRef.refersTo(entity)) continue;
it.remove();
}
}
public void persistNeighbours() {
pruneInvalidBackRefs();
for (NodeBacked backref : backrefs) {
backref.persist();
}
}
}

View File

@@ -108,7 +108,6 @@ public abstract class DefaultEntityState<ENTITY extends GraphBacked<STATE>, STAT
}
}
protected Object getIdFromEntity() {
final Field idField = fieldAccessorFactoryProviders.getIdField();
if (idField==null) return null;
@@ -120,9 +119,4 @@ public abstract class DefaultEntityState<ENTITY extends GraphBacked<STATE>, STAT
return null;
}
}
@Override
public boolean refersTo(GraphBacked target) {
return false;
}
}

View File

@@ -19,15 +19,17 @@ package org.springframework.data.graph.neo4j.fieldaccess;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.neo4j.graphdb.Transaction;
import org.springframework.data.graph.annotation.RelatedTo;
import org.springframework.data.graph.core.EntityState;
import org.springframework.data.graph.core.GraphBacked;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.core.EntityState;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.data.graph.neo4j.support.DoReturn.unwrap;
@@ -40,11 +42,9 @@ public class DetachedEntityState<ENTITY extends GraphBacked<STATE>, STATE> imple
protected final EntityState<ENTITY,STATE> delegate;
private final static Log log = LogFactory.getLog(DetachedEntityState.class);
private GraphDatabaseContext graphDatabaseContext;
private final BackReferences backReferences = null;
public DetachedEntityState(final EntityState<ENTITY, STATE> delegate, GraphDatabaseContext graphDatabaseContext) {
this.delegate = delegate;
this.graphDatabaseContext = graphDatabaseContext;
//this.backReferences = new BackReferences(this);
}
@Override
@@ -152,14 +152,31 @@ public class DetachedEntityState<ENTITY extends GraphBacked<STATE>, STATE> imple
// createAndAssignState();
throw new IllegalStateException("Flushing detached entity without a persistent state, this had to be created first.");
}
if (isDirty()) {
for (final Map.Entry<Field, ExistingValue> entry : dirty.entrySet()) {
final Field field = entry.getKey();
if (log.isDebugEnabled()) log.debug("Flushing dirty Entity new node " + entity.getPersistentState() + " field " + field+ " with value "+getValueFromEntity(field));
checkConcurrentModification(entity, entry, field);
delegate.setValue(field, getValueFromEntity(field));
}
final Map<Field, ExistingValue> dirtyCopy = new HashMap<Field, ExistingValue>(dirty);
clearDirty();
for (final Map.Entry<Field, ExistingValue> entry : dirtyCopy.entrySet()) {
final Field field = entry.getKey();
Object valueFromEntity = getValueFromEntity(field);
cascadePersist(valueFromEntity);
if (log.isDebugEnabled()) log.debug("Flushing dirty Entity new node " + entity.getPersistentState() + " field " + field+ " with value "+ valueFromEntity);
checkConcurrentModification(entity, entry, field);
delegate.setValue(field, valueFromEntity);
}
}
}
private void cascadePersist(Object valueFromEntity) {
if (valueFromEntity instanceof NodeBacked) {
((NodeBacked) valueFromEntity).persist();
}
if (valueFromEntity instanceof Collection) {
for (Object o : (Collection<Object>)valueFromEntity) {
if (o instanceof NodeBacked) {
((NodeBacked) o).persist();
}
}
}
}
@@ -217,7 +234,6 @@ public class DetachedEntityState<ENTITY extends GraphBacked<STATE>, STATE> imple
Transaction tx = graphDatabaseContext.beginTx();
try {
ENTITY result = delegate.persist();
//persistNeighbours();
flushDirty();
tx.success();
@@ -226,46 +242,4 @@ public class DetachedEntityState<ENTITY extends GraphBacked<STATE>, STATE> imple
tx.finish();
}
}
private void persistNeighbours() {
backReferences.persistNeighbours();
for (NodeBacked nodeBacked : getOutboundDirtyNodeEntities()) {
nodeBacked.persist();
}
}
private Set<NodeBacked> getOutboundDirtyNodeEntities() {
HashSet<NodeBacked> result = new HashSet<NodeBacked>();
for (Field field : dirty.keySet()) {
if (handleSingleField(result, field)) continue;
handleOneToMany(result, field);
}
return result;
}
private boolean handleOneToMany(HashSet<NodeBacked> result, Field field) {
if ((Collection.class.isAssignableFrom(field.getType())) && field.isAnnotationPresent(RelatedTo.class)) {
result.addAll((Collection) getValueFromEntity(field));
return true;
}
return false;
}
private boolean handleSingleField(HashSet<NodeBacked> result, Field field) {
if (NodeBacked.class.isAssignableFrom(field.getType())) {
Object value = getValueFromEntity(field);
if (value!=null) {
result.add((NodeBacked) value);
}
return true;
}
return false;
}
public boolean refersTo(GraphBacked target) {
return getOutboundDirtyNodeEntities().contains(target);
}
}

View File

@@ -119,9 +119,6 @@ public aspect Neo4jNodeBacking { // extends AbstractTypeAnnotatingMixinFields<No
public <T extends NodeBacked> T NodeBacked.persist() {
return (T)this.entityState.persist();
}
public boolean NodeBacked.refersTo(GraphBacked target) {
return this.entityState.refersTo(target);
}
public void NodeBacked.setPersistentState(Node n) {
if (this.entityState == null) {

View File

@@ -67,6 +67,10 @@ class HasRelationshipMatcher extends TypeSafeMatcher<Node>
{
description.appendText( "Expected relationship named " + relationshipTypeName + " to " +(other==null ? "unspecified": other)+"\r\n got: " );
if (relationships == null) {
description.appendValue("[]");
return;
}
List<String> types = new ArrayList<String>();
for ( Relationship rel : relationships )
{

View File

@@ -23,7 +23,10 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.NotInTransactionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.graph.neo4j.Friendship;
import org.springframework.data.graph.neo4j.Group;
import org.springframework.data.graph.neo4j.Person;
import org.springframework.data.graph.neo4j.repository.DirectGraphRepositoryFactory;
@@ -43,7 +46,6 @@ import static org.springframework.data.graph.neo4j.support.HasRelationshipMatche
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml"} )
public class ModificationOutsideOfTransactionTest
{
@@ -71,7 +73,6 @@ public class ModificationOutsideOfTransactionTest
}
@Test
@Ignore("ignored until subgraph persisting is added")
public void testCreateSubgraphOutsideOfTransactionPersistInDirectionOfRel() {
Person michael = new Person("Michael", 35);
Person emil = new Person("Emil", 31);
@@ -87,6 +88,48 @@ public class ModificationOutsideOfTransactionTest
}
@Test
public void testCreateSubgraphOutsideOfTransactionPersistWithImmediateCycle() {
Person michael = new Person("Michael", 35);
Person emil = new Person("Emil", 31);
michael.setBoss(emil);
emil.setBoss(michael);
assertEquals(emil, michael.getBoss());
assertEquals(michael, emil.getBoss());
assertFalse(hasPersistentState(michael));
assertFalse(hasPersistentState(emil));
michael.persist();
assertThat(nodeFor(michael), hasRelationship("boss", nodeFor(emil)));
assertThat(nodeFor(emil), hasRelationship("boss", nodeFor(michael)));
}
@Test
public void testCreateSubgraphOutsideOfTransactionPersistWithCycle() {
Person michael = new Person("Michael", 35);
Person david = new Person("David", 27);
Person emil = new Person("Emil", 31);
michael.setBoss(emil);
david.setBoss(michael);
emil.setBoss(david);
assertEquals(emil, michael.getBoss());
assertEquals(michael, david.getBoss());
assertEquals(david, emil.getBoss());
assertFalse(hasPersistentState(michael));
assertFalse(hasPersistentState(david));
assertFalse(hasPersistentState(emil));
michael.persist();
assertThat(nodeFor(michael), hasRelationship("boss", nodeFor(emil)));
assertThat(nodeFor(michael), hasRelationship("boss", nodeFor(david)));
assertThat(nodeFor(david), hasRelationship("boss", nodeFor(michael)));
assertThat(nodeFor(david), hasRelationship("boss", nodeFor(emil)));
assertThat(nodeFor(emil), hasRelationship("boss", nodeFor(david)));
assertThat(nodeFor(emil), hasRelationship("boss", nodeFor(michael)));
}
@Ignore("ignored until subgraph persisting is added")
@Test
public void testCreateSubgraphOutsideOfTransactionPersistInReverseDirectionOfRel() {
@@ -103,6 +146,14 @@ public class ModificationOutsideOfTransactionTest
assertThat(nodeFor(emil), hasRelationship("boss", nodeFor(michael)));
}
// TODO: Would be nice if this worked outside of a tx
@Test(expected = NotInTransactionException.class)
public void foo() {
Person p = persistedPerson("Michael", 35);
Person p2 = persistedPerson("David", 26);
Friendship f = p.knows(p2);
}
@Test
public void testSetPropertyOutsideTransaction()
{

View File

@@ -64,7 +64,7 @@
indexes (via the IndexManager), database life cycle callbacks, transaction management, and more.
</para>
<para>
The EmbeddedGraphDatabaseService is an implementation of GraphDatabaseService that is used to
The <code>EmbeddedGraphDatabase</code> is an implementation of GraphDatabaseService that is used to
embed Neo4j in a Java application. This implementation is used so as to provide the highest
and tightest integration with the database. Besides the embedded mode, the
<ulink url="http://wiki.neo4j.org/content/Getting_Started_With_Neo4j_Server">Neo4j server</ulink>

View File

@@ -5,7 +5,7 @@
<para>
Behind the scenes, Spring Data Graph leverages <ulink url="http://www.eclipse.org/aspectj/">AspectJ</ulink>
aspects to modify the behavior of simple annotated POJO entities
(see <xref linkend="reference:aspectj-intro"/>). Each node entity is backed by a graph node that holds its
(see <xref linkend="reference:aspectj-details"/>). Each node entity is backed by a graph node that holds its
properties and relationships to other entities. AspectJ is used for intercepting field access, so that
Spring Data Graph can retrieve the information from the entity's backing node or relationship in the database.
</para>

View File

@@ -1,36 +1,129 @@
<?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>
<title>Detached entities</title>
<title>Detached node entities</title>
<para>
By default newly created node entities are in a detached state. When <code>persist()</code> is called on the
entity it is attached to the graph store and its properties and relationships are persisted as well. Changing
an attached entity inside a transaction will write through the changes to the datastore. Whenever an entity
is changed outside of a transaction it will be considered detached. The changed data is stored in the entity
itself and not written back to the datastore.
Node entities can be in two different persistence state: 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
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.
</para>
<para>
All entities that are returned by library functions are initially in an attached state. Changing them outside
of a transaction detaches them. For writing the changes back it is necessary to <code>persist()</code> them
again.
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>.
</para>
<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
attached entities. The persist operation creates its own, implicit transaction. When it is called withina
external transaction it participates otherwise it is an atomic operation.
All entities returned by library functions are initially in an attached state.
Just as with any other entity, changing them outside of a transaction detaches them, and they
must be reattached with <code>persist()</code> for the data to be saved.
</para>
<para>
Please keep in mind that the session handling behaviour is still heavily developed. The defaults and also
other aspects of the behaviour are likely to change in subsequent releases. At the moment there is no support
for the creation of relationships outside of transactions and also more complex operations like creating
whole subgraphs outside of transactions is not supported.
</para>
<programlisting language="java"><![CDATA[
@NodeEntity
<!--<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-->
<!--attached entities. The persist operation creates its own, implicit transaction. When it is called withina-->
<!--external transaction it participates otherwise it is an atomic operation.-->
<!--</para>-->
<example>
<title>Persisting entities</title>
<programlisting language="java"><![CDATA[@NodeEntity
class Person {
String name;
Person(String name) { this.name = name; }
}
Person p = new Person().persist();
// Store Michael in the database.
Person p = new Person("Michael").persist();
]]></programlisting>
</example>
<section id="reference:programming-model:detached:relating">
<title>Relating detached entities</title>
<para>
As mentioned above, an entity simply created with the <code>new</code> keyword starts out detached.
It also has no state assigned to it. If you create a new entity with <code>new</code> and then throw
it away, the database won't be touched at all.
</para>
<para>
Now consider this scenario:
<example>
<title>Relationships outside of transactions</title>
<programlisting><![CDATA[@NodeEntity
class Movie {
private Actor topActor;
public void setTopActor(Actor actor) {
topActor = actor;
}
}
@NodeEntity
class Actor {
}
Movie movie = new Movie();
Actor actor = new Actor();
movie.setTopActor(actor);
]]></programlisting>
</example>
</para>
<para>
Neither the actor nor the movie has been assigned a node in the graph. If we were to call
<code>movie.persist()</code>, then Spring Data Graph would first create a node for the movie.
It would then note that there is a relationship to an actor, so it would call actor.persist()
in a cascading fashion. Once the actor has been persisted, it will create the relationship
from the movie to the actor. All of this will be done atomically in one transaction.
</para>
<para>
Important to note here is that if <code>actor.persist()</code> is called instead, then only
the actor will be persisted. The reason for this is that the actor entity knows nothing about
the movie entity. It is the movie entity that has the reference to the actor. Also note that
this behavior is not dependent on any configured relationship direction on the annotations.
It is a matter of Java references and is not related to the data model in the database.
</para>
<para>
If the relationships form a cycle, then the entities will first all be assigned a node in
the database, and then the relationships will be created. The cascading of <code>persist()</code>
is however only cascaded to related entity fields that have been modified.
</para>
<para>
In the following example, the actor and the movie are both attached entites, having both been
previously persisted to the graph:
<example>
<title>Cascade for modified fields</title>
<programlisting><![CDATA[actor.setName("Billy Bob");
movie.persist();
]]></programlisting>
</example>
In this case, even though the movie has a reference to the actor, the name change on the actor
will not be persisted by the call to <code>movie.persist()</code>. The reason for this is, as
mentioned above, that cascading will only be done for fields that have been modified. Since the
<code>movie.topActor</code> field has not been modified, it will not cascade the persist operation
to the actor.
</para>
</section>
<!--<para>-->
<!--Please keep in mind that the detached behavior is still being heavily developed. The defaults and-->
<!--other aspects of the behavior are likely to change in subsequent releases. At the moment there-->
<!--is no support for the creation of relationships outside of transactions. More complex operations-->
<!--like creating whole subgraphs outside of transactions is not supported.-->
<!--</para>-->
<!--<note>-->
<!--<para>-->
<!--</para>-->
<!--<para>-->
<!--In certain cases, it is actually possible to create relationships outside of a-->
<!--transactional context. If the entities and their relationships form a <ulink-->
<!--url="http://en.wikipedia.org/wiki/Directed_acyclic_graph">DAG (Directed Acyclic Graph)</ulink>,-->
<!--then they can be persisted <emphasis>if</emphasis> <code>persist()</code> is called on every entity-->
<!--<emphasis>in reverse order from the leaf nodes</emphasis>.-->
<!--</para>-->
<!--<para>-->
<!--For example, assume that instances of entity classes A, B, and C are linked like so:-->
<!--<code>B2 &lt;- A -> B1 -> C</code>. In order to persist this graph without transactions, one would-->
<!--first have to persist <code>C</code>, then <code>B1</code> and <code>B2</code>, and finally-->
<!--<code>A</code>. If one wants to use this-->
<!--</para>-->
<!--</note>-->
</section>

View File

@@ -3,22 +3,24 @@
<section>
<title>Bean validation (JSR-303)</title>
<para>
Spring Data Graph supports property based validation support. So, whenever a property is changed, it is
checked against the annotated constraints (.e.g @Min, @Max, @Size, etc).
Validation errors throw a ValidationException. For evaluating the constraints the validation support that
comes with Spring is used. To use it a validator has to be registered with the GraphDatabaseContext, if there
is none, no validation will be performed (any registered Validator or (Local)ValidatorFactoryBean will be
used).
Spring Data Graph supports property-based validation support. When a property is changed, 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>.
</para>
<programlisting language="java"><![CDATA[
<example>
<title>Bean validation</title>
<programlisting language="java"><![CDATA[
@NodeEntity
class Person {
@Size(min = 3, max = 20)
String name;
@Min(0)
@Max(100)
@Min(0) @Max(100)
int age;
}
]]></programlisting>
</example>
</section>

View File

@@ -29,7 +29,8 @@
package.
</para>
<para>
The indexes can be queried by using a repository (see <xref linkend="reference:repositories" />).
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.graph.neo4j.repository.DirectGraphRepositoryFactory</code>.
The methods <code>findByPropertyValue()</code> and <code>findAllByPropertyValue()</code> work on
@@ -45,7 +46,8 @@ class Person {
@Indexed int age;
}
GraphRepository<Person> graphRepository = graphRepositoryFactory.createGraphRepository(Person.class);
GraphRepository<Person> graphRepository = graphRepositoryFactory
.createGraphRepository(Person.class);
// Exact match, in named index
Person mark = graphRepository.findByPropertyValue("people", "name", "mark");
@@ -84,7 +86,8 @@ class Person {
@Indexed(indexName = "person-name", fulltext=true) String name;
}
GraphRepository<Person> graphRepository = graphRepositoryFactory.createGraphRepository(Person.class);
GraphRepository<Person> graphRepository = graphRepositoryFactory
.createGraphRepository(Person.class);
Person mark = graphRepository.findAllByQuery("people-search", "name", "ma*");
]]></programlisting>

View File

@@ -9,92 +9,90 @@
<section>
<title>@NodeEntity: The basic building block</title>
<para>
The <code>@NodeEntity</code> annotation is used to declare a POJO entity to be backed by a node in the
graph store. Simple fields on the entity are mapped by default to properties of the node. Object
references to other NodeEntities (whether single or Collection) are mapped via relationships. If
the annotation parameter <code>useShortNames</code> is set to false, the properties and relationship
names used will be prepended with the class name of the entity.
</para><para>
If the <code>partial</code>
parameter is set to true, this entity takes part in a cross-store setting /<xref linkend="cross-store"/>)
where only the specifically annotated parts of the entity not handled by JPA will be mapped to the graph store.
The <code>@NodeEntity</code> annotation is used to turn a POJO class into an entity backed by a node
in the graph database. Fields on the entity are by default mapped to properties of the node. Fields
referencing other node entities (or collections thereof) are linked with relationships. If the
<code>useShortNames</code> attribute overridden to false, the property and relationship names will
have the class name of the entity prepended.
</para>
<para>Entity fields can be annotated with @GraphProperty, @RelatedTo, @RelatedToVia, @Indexed, @GraphId and
@GraphTraversal.
<para>
If the <code>partial</code> attribute is set to true, this entity takes part in a cross-store setting,
where the entity lives in both the graph database and a JPA data source. See
<xref linkend="cross-store"/> for more information.
</para>
<example>
<title>Simple Node Entity</title>
<programlisting language="java"><![CDATA[
// simplest example
@NodeEntity
<para>
Entity fields can be annotated with <code>@GraphProperty</code>, <code>@RelatedTo</code>,
<code>@RelatedToVia</code>, <code>@Indexed</code>, <code>@GraphId</code> and
<code>@GraphTraversal</code>.
</para>
<example>
<title>Simple node entity</title>
<programlisting language="java"><![CDATA[@NodeEntity
public class Movie {
String title;
String title;
}
]]></programlisting>
</example>
</example>
</section>
<section>
<title>@GraphProperty: Optional Annotation for Property Fields</title>
<para>It is not necessary to annotate fields as they are persisted by default; all fields that contain primitive
values are persisted directly to the graph. All fields
convertible to String using the Spring conversion services will be stored as a string.
(Spring Data Graph adds a custom conversion factory that comes with converters for Enums and Dates).
<title>@GraphProperty: Optional annotation for property fields</title>
<para>
It is not necessary to annotate data fields, as they are persisted by default; all fields that
contain primitive values are persisted directly to the graph. All fields convertible to String
using the Spring conversion services will be stored as a string. Spring Data Graph includes a
custom conversion factory that comes with converters for <code>Enum</code>s and <code>Date</code>s.
Transient fields are not persisted.
This annotation is mainly used for cross-store persistence.
</para>
<para>
This annotation is typically used with cross-store persistence. When a node entity is configured
as partial, then all fields that should be persisted to the graph must be explicitly annotated
with <code>@GraphProperty</code>.
</para>
</section>
<section>
<title>@Indexed: Making entities searchable by field value</title>
<para>The @Indexed annotation can be declared on fields that are intended to be indexed by the Neo4j
indexing facilities, triggered by value modification.
The resulting index can be used to later retrieve nodes or relationships that contain a certain property
value (for example a name). Often an index is used to establish the start node for a traversal.
Indexes are accessed by a <code>Repository</code> for a particular node or relationship entity, created via a
<code>DirectGraphRepositoryFactory</code>.
</para>
<para>
GraphDatabaseContext exposes the indexes for Nodes and Relationships via the <code>getIndex</code> method.
Index names default to the domain class
name, but can also be named (<code>indexName</code> attribute)individually to reflect domain concepts.
be named, for instance to keep separate domain concepts in separate indexes.
</para>
<para>
Numerical values are indexed as such by default, allowing for range queries.
Fulltext indexing is also possible by setting the <code>fulltext</code> attribute to true. For details see
the indexing section <xref linkend="reference:programming-model:indexing"/>.
The @Indexed annotation can be declared on fields that are intended to be indexed by the Neo4j
indexing facilities. The resulting index can be used to later retrieve nodes or relationships
that contain a certain property value, e.g. a name. Often an index is used to establish the start
node for a traversal. Indexes are accessed by a repository for a particular node or relationship
entity type. See <xref linkend="reference:programming-model:indexing"/> and
<xref linkend="reference:programming-model:repositories"/> for more information.
</para>
</section>
<section>
<title>@GraphTraversal: fields providing direct access to traversal results</title>
<para>The @GraphTraversal annotation leverages the delegation infrastructure used by the Spring Data Graph
aspects. It provides dynamic fields which, when accessed, return an Iterable of NodeEntities that are
the result of a traversal starting at the current NodeEntity. The TraversalDescription used for this
is created by a TraversalDescriptionBuilder whose class is referred to by the <code>traversalBuilder</code>
attribute of the annotation. The class of the expected NodeEntities is provided with the
<title>@GraphTraversal: fields as traversal result views</title>
<para>
The <code>@GraphTraversal</code> annotation leverages the delegation infrastructure used by the
Spring Data Graph aspects. It provides dynamic fields which, when accessed, return an Iterable
of node entities that are the result of a traversal starting at the entity containing the field.
The <code>TraversalDescription</code> used for this is created by the
<code>FieldTraversalDescriptionBuilder</code> class defined by the <code>traversalBuilder</code>
attribute. The class of the resulting node entities must be provided with the
<code>elementClass</code> attribute.
<example>
<title>@GraphTraversal in a Node Entity</title>
<programlisting language="java"><![CDATA[
@NodeEntity
</para>
<example>
<title>@GraphTraversal from a node entity</title>
<programlisting language="java"><![CDATA[@NodeEntity
public class Group {
@GraphTraversal(traversalBuilder = PeopleTraversalBuilder.class,
elementClass = Person.class, params = "persons")
private Iterable<Person> people;
@GraphTraversal(traversalBuilder = PeopleTraversalBuilder.class,
elementClass = Person.class, params = "persons")
private Iterable<Person> people;
private static class PeopleTraversalBuilder implements FieldTraversalDescriptionBuilder {
@Override
public TraversalDescription build(NodeBacked start, Field field, String...params) {
return new TraversalDescriptionImpl()
.relationships(DynamicRelationshipType.withName(params[0]))
.filter(Traversal.returnAllButStartNode());
private static class PeopleTraversalBuilder implements FieldTraversalDescriptionBuilder {
@Override
public TraversalDescription build(NodeBacked start, Field field, String... params) {
return new TraversalDescriptionImpl()
.relationships(DynamicRelationshipType.withName(params[0]))
.filter(Traversal.returnAllButStartNode());
}
}
}
}
]]></programlisting>
</example>
</para>
</example>
</section>
</section>

View File

@@ -9,7 +9,7 @@
Spring Data Graph has special support to represent Neo4j relationships as entities too, but it is often
not needed.
</para>
<section>
<section id="reference:programming_model:relationships:relatedto">
<title>@RelatedTo: Connecting node entities</title>
<para>
Every field of a node entity that references one or more other node entities is backed by relationships
@@ -45,7 +45,8 @@ public class Movie {
<programlisting language="java"><![CDATA[
@NodeEntity
public class Actor {
@RelatedTo(type = "mostPaidActor", direction = Direction.INCOMING, elementClass = Movie.class)
@RelatedTo(type = "mostPaidActor", direction = Direction.INCOMING,
elementClass = Movie.class)
private Set<Movie> mostPaidIn;
@RelatedTo(type = "ACTS_IN", elementClass = Movie.class)

View File

@@ -1,6 +1,6 @@
<?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:repositories">
<section id="reference:programming-model:repositories">
<title>CRUD with repositories</title>
<para>
The repositories provided by Spring Data Graph build on the composable repository infrastructure
@@ -8,17 +8,17 @@
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 Graph 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>
<!--<note>-->
<!--<para>-->
<!--Spring Data Graph 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 Graph comes with typed repository implementations that provide methods for
locating node and relationship entities. There are 3 types of basic repository interfaces
@@ -27,105 +27,136 @@
indexing subsystem for queries, and <code>TraversalRepository</code> handles Neo4j traversals.
</para>
<para>
<code>CRUDRepository</code> delegates to the configured <code>TypeRepresentationStrategy</code>
(see <xref linkend="reference:programming-model:typerepresentationstrategy"/>)
for type based queries.
<variablelist>
<varlistentry>
<term>Load an instance via a Neo4j node id</term>
<listitem><para><code>T findOne(id)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Check for existence of a Neo4j node id</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>
</varlistentry>
<varlistentry>
<term>Count the instances of a node entity type</term>
<listitem><para><code>Long count()</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Save a graph entity</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>
<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>
<code>IndexRepository</code> works with the indexing subsystem and provides methods to find
entities by indexed properties, ranged queries, and combinations thereof. The index key is
the name of the indexed entity field, unless overridden in the <code>@Indexed</code> annotation.
<variablelist>
<varlistentry>
<term>Iterate over all indexed entity instances with a certain field value</term>
<listitem><para><code>Iterable&lt;T> findAllByPropertyValue(key, value)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Get a single entity instance with a certain field value</term>
<listitem><para><code>T findByPropertyValue(key, value)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Iterate over all indexed entity instances with field values in a certain numerical range (inclusive)</term>
<listitem><para><code>Iterable&lt;T> findAllByRange(key, from, to)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Iterate over all indexed entity instances with field values matching the given fulltext string or QueryContext query</term>
<listitem><para><code>Iterable&lt;T> findAllByQuery(key, queryOrQueryContext)</code></para></listitem>
</varlistentry>
</variablelist>
There is also a <code>NamedIndexRepository</code> with the same methods, but with an additional index
name parameter, making it possible to query any index.
<code>GraphRepository</code> is a convenience repository interface, extending <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.
</para>
<para>
<code>TraversalRepository</code> delegates to the Neo4j traversal framework.
<variablelist>
<varlistentry>
<term>Iterate over a traversal result</term>
<listitem><para><code>Iterable&lt;T> findAllByTraversal(startEntity, traversalDescription)</code></para></listitem>
</varlistentry>
</variablelist>
</para>
<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.
</para>
<example>
<title>Using GraphRepositories</title>
<programlisting language="java"><![CDATA[
GraphRepository<Person> graphRepository = graphRepositoryFactory.createGraphRepository(Person.class);
<section>
<title>CRUDRepository</title>
<para>
<code>CRUDRepository</code> delegates to the configured <code>TypeRepresentationStrategy</code>
(see <xref linkend="reference:programming-model:typerepresentationstrategy"/>)
for type based queries.
<variablelist>
<varlistentry>
<term>Load an instance via a Neo4j node id</term>
<listitem><para><code>T findOne(id)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Check for existence of a Neo4j node id</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>
</varlistentry>
<varlistentry>
<term>Count the instances of a node entity type</term>
<listitem><para><code>Long count()</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Save a graph entity</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>
<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>
Person michael = graphRepository.save(new Person("Michael", 36));
<section>
<title>IndexRepository and NamedIndexRepository</title>
<para>
<code>IndexRepository</code> works with the indexing subsystem and provides methods to find
entities by indexed properties, ranged queries, and combinations thereof. The index key is
the name of the indexed entity field, unless overridden in the <code>@Indexed</code> annotation.
<variablelist>
<varlistentry>
<term>Iterate over all indexed entity instances with a certain field value</term>
<listitem><para><code>Iterable&lt;T> findAllByPropertyValue(key, value)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Get a single entity instance with a certain field value</term>
<listitem><para><code>T findByPropertyValue(key, value)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Iterate over all indexed entity instances with field values in a certain numerical range (inclusive)</term>
<listitem><para><code>Iterable&lt;T> findAllByRange(key, from, to)</code></para></listitem>
</varlistentry>
<varlistentry>
<term>Iterate over all indexed entity instances with field values matching the given fulltext string or QueryContext query</term>
<listitem><para><code>Iterable&lt;T> findAllByQuery(key, queryOrQueryContext)</code></para></listitem>
</varlistentry>
</variablelist>
</para>
<para>
There is also a <code>NamedIndexRepository</code> with the same methods, but with an additional index
name parameter, making it possible to query any index.
</para>
Person dave = graphRepository.findOne(123);
</section>
Long numberOfPeople = graphRepository.count();
<section>
<title>TraversalRepository</title>
<para>
<code>TraversalRepository</code> delegates to the Neo4j traversal framework.
<variablelist>
<varlistentry>
<term>Iterate over a traversal result</term>
<listitem><para><code>Iterable&lt;T> findAllByTraversal(startEntity, traversalDescription)</code></para></listitem>
</varlistentry>
</variablelist>
</para>
</section>
Person mark = graphRepository.findByPropertyValue("name", "mark");
<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.
</para>
<example>
<title>Using GraphRepositories</title>
<programlisting language="java"><![CDATA[
GraphRepository<Person> graphRepository = graphRepositoryFactory
.createGraphRepository(Person.class);
Iterable<Person> devs = graphRepository.findAllByProperyValue("occupation", "developer");
Person michael = graphRepository.save(new Person("Michael", 36));
Iterable<Person> middleAgedPeople = graphRepository.findAllByRange("age", 20, 40);
Person dave = graphRepository.findOne(123);
Iterable<Person> aTeam = graphRepository.findAllByQuery("name", "A*");
Long numberOfPeople = graphRepository.count();
Person mark = graphRepository.findByPropertyValue("name", "mark");
Iterable<Person> devs = graphRepository.findAllByProperyValue("occupation", "developer");
Iterable<Person> middleAgedPeople = graphRepository.findAllByRange("age", 20, 40);
Iterable<Person> aTeam = graphRepository.findAllByQuery("name", "A*");
Iterable<Person> davesFriends = graphRepository.findAllByTraversal(dave,
Traversal.description().pruneAfterDepth(1)
.relationships(KNOWS).filter(returnAllButStartNode()));
]]></programlisting>
</example>
</section>
Iterable<Person> davesFriends = graphRepository.findAllByTraversal(dave,
Traversal.description().pruneAfterDepth(1)
.relationships(KNOWS).filter(returnAllButStartNode()));
]]></programlisting>
</example>
<section>
<title>Composing repositories</title>
<para>
@@ -140,8 +171,9 @@ Iterable<Person> davesFriends = graphRepository.findAllByTraversal(dave,
public interface PersonRepository extends GraphRepository<Person>, PersonRepositoryExtension {}
// alternatively select some of the required repositories individually
public interface PersonRepository extends CRUDGraphRepository<Node,Person>, IndexQueryExecutor<Node,Person>,
TraversalQueryExecutor<Node,Person>, PersonRepositoryExtension {}
public interface PersonRepository extends CRUDGraphRepository<Node,Person>,
IndexQueryExecutor<Node,Person>, TraversalQueryExecutor<Node,Person>,
PersonRepositoryExtension {}
// provide a custom extension if needed
public interface PersonRepositoryExtension {
@@ -156,8 +188,10 @@ public class PersonRepositoryImpl implements PersonRepositoryExtension {
}
}
// configure the repositories, preferably via the datagraph:repositories namespace (graphDatabaseContext reference is optional)
<datagraph:repositories base-package="org.springframework.data.graph.neo4j" graph-database-context-ref="graphDatabaseContext"/>
// configure the repositories, preferably via the datagraph:repositories namespace
// (graphDatabaseContext reference is optional)
<datagraph:repositories base-package="org.springframework.data.graph.neo4j"
graph-database-context-ref="graphDatabaseContext"/>
// have it injected
@Autowired

View File

@@ -1,7 +1,7 @@
<?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:typerepresentationstrategy">
<title>Entity types stored</title>
<title>Entity type representation</title>
<para>
There are several ways to represent the Java type hierarchy of the data model in the graph. In general, for all
node and relationship entities, type information is needed to perform certain repository operations. Some of

View File

@@ -2,86 +2,101 @@
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="samples">
<title>Sample code</title>
<title>Sample code</title>
<section id="samples_introduction">
<title>Introduction</title>
<para>
Spring Data Graph comes with a number of samples.
The source code of the samples is found on
<ulink url="http://github.com/SpringSource/spring-data-graph-examples">GitHub</ulink>.
The different sample projects are introduced below.
</para>
</section>
<section id="samples:introduction">
<title>Introduction</title>
<para>
Spring Data Graph comes with a number of sample applications. The source code of the samples can be found on
<ulink url="http://github.com/SpringSource/spring-data-graph-examples">Github</ulink>. The different sample
projects are introduced below.
</para>
</section>
<section id="samples_hello-worlds">
<title>Hello Worlds sample</title>
<para>The Hello Worlds sample application is a simple console application with unit tests, that
creates some Worlds (entities / nodes) and Rocket Routes (relationships) in a Galaxy (graph)
and then reads them back and prints them out.</para>
<para>The unit tests demonstrate some other features of Spring Data Graph. The sample comes with a
minimal configuration for Maven and Spring to get up and running quickly.</para>
<para>Executing the application creates the following graph in the Graph Database:</para>
<mediaobject>
<imageobject>
<imagedata fileref="helloworlds.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</section>
<section id="samples:hello-worlds">
<title>Hello Worlds sample application</title>
<para>
The Hello Worlds sample application is a simple console application. It creates some worlds
(node entities) and rocket routes (relationships) between worlds, all in a galaxy (the graph),
and then prints them.
</para>
<para>
The unit tests demonstrate some other features of Spring Data Graph as well. The sample comes
with a minimal configuration for Maven and Spring to get up and running quickly.
</para>
<para>
Executing the application creates the following graph in the graph database:
</para>
<mediaobject>
<imageobject>
<imagedata fileref="helloworlds.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</section>
<section id="samples_imdb">
<title>IMDB sample</title>
<para>A web application that imports datasets from the Internet Movie Database (IMDB) into
the
graph database. It allows listings of movies with their actors and actors with their roles in
different movies. It also uses graph traversal operations to calculate the Kevin Bacon number
(distance to an actor that has acted with Kevin Bacon). This sample application shows the
basic usage of Spring Data Graph in a more complex setting with several annotated entities and
relationships as well as usage of indices and graph traversal.</para>
<para>See the readme file for instruction on how to compile and run the application.</para>
<para>An excerpt of the data stored in the Graph Database after executing the application:
</para>
<para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata fileref="imdb.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</screenshot>
</para>
</section>
<section id="samples:imdb">
<title>IMDB sample application</title>
<para>
The IMDB sample is a web application that imports datasets from the Internet Movie Database (IMDB)
into the graph database. It allows the listing of movies with their actors, and of actors and their
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 Graph in a more complex setting, using several
annotated entities and relationships as well as indexes and graph traversals.
</para>
<para>
See the readme file for instructions on how to compile and run the application.
</para>
<para>
An excerpt of the data stored in the graph database after executing the application:
</para>
<para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata fileref="imdb.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</screenshot>
</para>
</section>
<section id="samples_myrestaurant-original">
<title>MyRestaurant sample</title>
<para>Simple, JPA based web application for managing users and restaurants, with the ability to add
restaurants as favorites to a user.</para>
<mediaobject>
<imageobject>
<imagedata fileref="restaurant.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</section>
<section id="samples:myrestaurants-original">
<title>MyRestaurants sample application</title>
<para>
Simple, JPA-based web application for managing users and restaurants, with the ability to add
restaurants as favorites to a user. It is basically the foundation for the MyRestaurants-Social
application (see<xref linkend="samples:myrestaurants-social" />), and does therefore not use
Spring Data Graph.
</para>
<mediaobject>
<imageobject>
<imagedata fileref="restaurant.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</section>
<section id="samples_myrestaurant-social">
<title>MyRestaurant-Social sample</title>
<para>An extended version of the MyRestaurant sample application that adds social networking
functionality to it. It is possible to have friends and to add rated relationships to
restaurants. The relationships and some of the properties of the entities are transparently
stored in the graph database. There is also a graph traversal that provides a recommendation
based on your friends' (and their friends') rating of restaurants. </para>
<para>An excerpt of the data stored in the Graph Database after executing the application:
</para>
<mediaobject>
<imageobject>
<imagedata fileref="restaurant-social.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="restaurant-social-graph.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</section>
<section id="samples:myrestaurants-social">
<title>MyRestaurant-Social sample application</title>
<para>
This application extends the MyRestaurants sample application, adding social networking
functionality to it with cross-store persistence. The web application allows for users to add
friends and rate restaurants. A graph traversal provides recommendations based on your friends'
(and their friends') rating of restaurants.
</para>
<para>
Here's an excerpt of the data stored in the graph database after executing the application:
</para>
<mediaobject>
<imageobject>
<imagedata fileref="restaurant-social.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="restaurant-social-graph.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</section>
</chapter>