Chapter 20. Programming model

This chapter covers the fundamentals of the programming model behind Spring Data Neo4j. It discusses the AspectJ features used and the annotations provided by Spring Data Neo4j and how to use them. Examples for this section are taken from the "IMDB" project of Spring Data Neo4j examples.

20.1. Object Graph Mapping

Until recently Spring Data Neo4j supported the only more advanced and flexible AspectJ based mapping approach, see Section 20.2, “AspectJ support”. Feedback about complications with the AspectJ tooling and other implications supported us in adding a simpler mapping (see Section 20.3, “Simple Object Graph Mapping”) to Spring Data Neo4j. Both versions work with the same annotations and provide similar API's but different behaviour.

Reflection and Annotation-based metadata is collected about persistent entities in the Neo4jMappingContext which provides it to any part of the library. The information is stored in Neo4jPersistentEntiy instances which hold all the Neo4jPersistentProperty's of the type. Each entity can be queried if it represents a Node or a Relationship. Properties declare detailed data about their indexing and relationship information as well as type information that also covers nested generic types. With all that information available it is simple to select the appropriate strategy for mapping each entity and field to elements, relationships and properties of the graph.

The main difference lies in the way of accessing the graph. In the conversion based mapping the required information is copied into the entity on load and only saved back when an explicit save operation occurs. In the AspectJ approach a node or relationship is stored in an additional field of the entity and all read- and write (inside of tx) access happens through that.

Otherwise the two approaches are sharing lots of the infrastructure. E.g. for creating new entity instances from type information store in the graph (Section 20.14, “Entity type representation”), the infrastructure for mapping individual fields to graph properties and relationships and everything related to indexing and querying. A certain part of that is also exposed via the Neo4jTemplate for direct use.

20.2. AspectJ support

Behind the scenes, Spring Data Neo4j leverages AspectJ aspects to modify the behavior of annotated POJO entities (see Chapter 25, 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 Neo4j can retrieve the information from the entity's backing node or relationship in the database.

The aspect introduces some internal fields and some public methods (see Section 20.11, “Introduced methods”) to the entities, such as entity.getPersistentState() and entity.relateTo. It also introduces repository methods likefind(Class<? extends NodeEntity>, TraversalDescription). Introduced methods for equals() and hashCode() use the underlying node or relationship.

Spring Data Neo4j internally uses an abstraction called EntityState that the field access and instantiation advices of the aspect delegate to. This way, the aspect code is kept to a minimum, focusing mainly on the pointcuts and delegation code. The EntityState then uses a number of FieldAccessorFactories to create a FieldAccessor instance per field that does the specific handling needed for the concrete field type. There are various layers of caching involved as well, so it handles repeated instantiation efficiently.

20.2.1. AspectJ IDE support

As Spring Data Neo4j uses some advanced features of AspectJ, users may experience issues with their IDE reporting errors where in fact there are none. Features that might be reported wrongfully include: introduction of methods to interfaces, declaration of additional interfaces for annotated classes, and generified introduced methods.

IDE's not providing the full AJ support might mark parts of your code as errors. You should rely on your build-system and test to verify the correctness of the code. You might also have your Entities (or their interfaces) implement the NodeBacked and RelationshipBacked interfaces directly to benefit from completion support and error checking.

Eclipse and STS support AspectJ via the AJDT plugin which can be installed from the update-site: http://download.eclipse.org/tools/ajdt/37/update/ (it might be necessary to use the latest development snapshot of the plugin http://download.eclipse.org/tools/ajdt/36/dev/update). The current version that does not show incorrect errors is AspectJ 1.6.12 (included in STS 2.8.0), previous versions are reported to mislead the user.

Note

There might be some issues with the eclipse maven plugin not adding AspectJ files correctly to the build path. If you encounter issues, please try the following: Try editing the build path to include **/*.aj for the spring-data-neo4j project. You can do this by selecting "Build Path -> Configure Build Path ..." from the Package Explorer. Then for the spring-data-neo4j/src/main/java add **/*.aj to the Included path. For importing an Spring Data Graph project into Eclipse with m2e. Please make sure that the AspectJ Configurator is installed and

The AspectJ support in IntelliJ IDEA lacks some of the features. JetBrains is working on improving the situation in their upcoming 11 release of their popular IDE. Their latest work is available under their early access program (EAP). Building the project with the AspectJ compiler ajc works in IDEA (Options -> Compiler -> Java Compiler should show ajc). Make sure to give the compiler at least 512 MB of RAM.

20.3. Simple Object Graph Mapping

The simple object graph mapping comes into action whenever an entity is constructed from a node or relationship. That could be explicitely like during the findOne or createNodeAs operations but also implicitely while executing any graph operation that returns nodes or relationships and expecting mapped entities to be returned.

It uses the available meta information about the persistent entity to iterate over its properties and relationships, fetching them from the graph while doing so. It also executes computed fields and stores the resulting values in the properties.

We try to avoid loading the whole graph into memory by not following too many relationships eagerly. A dedicated @Fetch annotation controls instead if related entities are loaded or not. Whenever an entity is not fully loaded, then only its id is stored. Those entities or collections of entities can then later be loaded explictely.

Example 20.1. Examples for loading entities from the graph

  @Autowired Neo4jOperations template;


  @NodeEntity class Person {
    String name;
    @Fetch Person boss;
    Person spouse;

    @RelatedTo(type = "FRIEND", direction = BOTH)
    @Fetch Set<Person> friends;
  }
  Person person = template.findOne(personId);
  assertNotNull(person.getBoss().getName());

  assertNotNull(person.getSpouse().getId());
  assertNull(person.getSpouse().getName());

  template.load(person.getSpouse());
  assertNotNull(person.getSpouse().getName());

  assertEquals(10,person.getFriends().size());
  assertNotNull(firstFriend.getName());


20.4. Defining node entities

Node entities are declared using the @NodeEntity annotation. Relationship entities use the @RelationshipEntity annotation.

20.4.1. @NodeEntity: The basic building block

The @NodeEntity 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 useShortNames attribute overridden to false, the property and relationship names will have the class name of the entity prepended.

@NodeEntity annotations are inherited from super-types and interfaces. It is not necessary to annotate your domain objects at every inheritance level.

If the partial 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 Chapter 22, Cross-store persistence for more information.

Entity fields can be annotated with @GraphProperty, @RelatedTo, @RelatedToVia, @Indexed, @GraphId and @GraphTraversal.

Example 20.2. Simple node entity

@NodeEntity
public class Movie {
    String title;
}

20.4.2. @GraphProperty: Optional annotation for property fields

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 Neo4j includes a custom conversion factory that comes with converters for Enums and Dates. Transient fields are not persisted.

Currently there is no support for handling arbitrary collections of primitive or convertable values. Support for this will be added by the 1.1. release.

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

20.4.3. @Indexed: Making entities searchable by field value

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 Section 20.6, “Indexing” and Section 20.8, “CRUD with repositories” for more information.

20.4.4. @Query: fields as query result views

The @Query annotation leverages the delegation infrastructure used by the Spring Data Neo4j aspects. It provides dynamic fields which, when accessed, return the values selected by the provided query language expression. The provided query must contain a placeholder named {self} for the id of the current entity. For instance start n=({self}) match n-[:FRIEND]->friend return friend. Graph queries can return variable number of entities. That's why annotation can be put onto fields with a single value, an Iterable of a concrete type or an Iterable of Map<String,Object>. Additional parameters are taken from the params attribute of the @Query annotation. The tuples form key-value pairs that are provided to the query at execution time.

Example 20.3. @Graph on a node entity field

@NodeEntity
public class Group {
    @Query(value = "start n=({self}) match (n)-[:%relType]->(friend) return friend",
                params = {"relType", "FRIEND"})
    private Iterable<Person> friends;
}

Note

Please note that this annotation can also be used on repository methods.

20.4.5. @GraphTraversal: fields as traversal result views

The @GraphTraversal annotation leverages the delegation infrastructure used by the Spring Data Neo4j 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 TraversalDescription used for this is created by the FieldTraversalDescriptionBuilder class defined by the traversalBuilder attribute. The class of the resulting node entities must be provided with the elementClass attribute.

Example 20.4. @GraphTraversal from a node entity

@NodeEntity
public class Group {
    @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());
        }
    }
}

20.5. Relating node entities

Since relationships are first-class citizens in Neo4j, associations between node entities are represented by relationships. In general, relationships are categorized by a type, and start and end nodes (which imply the direction of the relationship). Relationships can have an arbitrary number of properties. Spring Data Neo4j has special support to represent Neo4j relationships as entities too, but it is often not needed.

Note

As of Neo4j 1.4.M03, circular references are allowed. Spring Data Neo4j reflects this accordingly.

20.5.1. @RelatedTo: Connecting node entities

Every field of a node entity that references one or more other node entities is backed by relationships in the graph. These relationships are managed by Spring Data Neo4j automatically.

The simplest kind of relationship is a single field pointing to another node entity (1:1). In this case, the field does not have to be annotated at all, although the annotation may be used to control the direction and type of the relationship. When setting the field, a relationship is created. If the field is set to null, the relationship is removed.

Example 20.5. Single relationship field

@NodeEntity
public class Movie {
    private Actor mostPaidActor;
}

It is also possible to have fields that reference a set of node entities (1:N). These fields come in two forms, modifiable or read-only. Modifiable fields are of the type java.util.Set<T>, and read-only fields are java.lang.Iterable<T>, where T is a @NodeEntity-annotated class.

Example 20.6. Node entity with relationships

@NodeEntity
public class Actor {
    @RelatedTo(type = "mostPaidActor", direction = Direction.INCOMING)
    private Set<Movie> mostPaidIn;

    @RelatedTo(type = "ACTS_IN")
    private Set<Movie> movies;
}

Fields referencing other entities should not be manually initialized, as they are managed by Spring Data Neo4j under the hood. 1:N fields can be accessed immediately, and Spring Data Neo4j will provide a java.util.Set representing the relationships. If the returned set is modified, the changes are reflected in the graph. Spring Data Neo4j also ensures that there is only one relationship of a given type between any two given entities.

Note

Before an entity has been attached with persist() for the first time, it will not have its state managed by Spring Data Neo4j. For example, given the Actor class defined above, if actor.movies was accessed in a non-persisted entity, it would return null, whereas if it was accessed in a persisted entity, it would return an empty managed set.

When you use an Interface as target type for the Set and/or as elementClass please make sure that it implements NodeBacked either by extending that Super-Interface manually or by annotating the Interface with @NodeEntity too.

By setting direction to BOTH, relationships are created in the outgoing direction, but when the 1:N field is read, it will include relationships in both directions. A cardinality of M:N is not necessary because relationships can be navigated in both directions.

The relationships can also be accessed by using the aspect-introduced methods entity.getRelationshipTo(target, type) and entity.relateTo(target, type) available on each NodeEntity. These methods find and create Neo4j relationships. It is also possible to manually remove relationships by using entity.removeRelationshipTo(target, type). Using these methods is significantly faster than adding/removing from the collection of relationships as it doesn't have to re-synchronize a whole set of relationships with the graph.

Note

Other collection types than Set are not supported so far, also currently NO Map<RelationshipType,Set<NodeBacked>>.

20.5.2. @RelationshipEntity: Rich relationships

To access the full data model of graph relationships, POJOs can also be annotated with @RelationshipEntity, making them relationship entities. Just as node entities represent nodes in the graph, relationship entities represent relationships. As described above, fields annotated with @RelatedTo provide a way to link node entities together via relationships, but it provides no way of accessing the relationships themselves.

Relationship entities cannot be instantiated directly but are rather created via node entities, either by @RelatedToVia-annotated fields (see Section 20.5.3, “@RelatedToVia: Accessing relationship entities”), or by the introduced entity.relateTo(target, relationshipClass, type) and entity.getRelationshipTo(target, relationshipClass, type) methods (see Section 20.11, “Introduced methods”).

Fields in relationship entities are, similarly to node entities, persisted as properties on the relationship. For accessing the two endpoints of the relationship, two special annotations are available: @StartNode and @EndNode. A field annotated with one of these annotations will provide read-only access to the corresponding endpoint, depending on the chosen annotation.

Example 20.7. Relationship entity

@NodeEntity
public class Actor {
    public Role playedIn(Movie movie, String title) {
        return relatedTo(movie, Role.class, "ACTS_IN");
    }
}

@RelationshipEntity
public class Role {
    String title;

    @StartNode private Actor actor;
    @EndNode private Movie movie;
}
        

20.5.3. @RelatedToVia: Accessing relationship entities

To provide easy programmatic access to the richer relationship entities of the data model, the annotation @RelatedToVia can be added on fields of type java.lang.Iterable<T>, where T is a @RelationshipEntity-annotated class. These fields provide read-only access to relationship entities.

Example 20.8. Accessing relationship entities using @RelatedToVia

@NodeEntity
public class Actor {
    @RelatedToVia(type = "ACTS_IN")
    private Iterable<Role> roles;

    public Role playedIn(Movie movie, String title) {
        Role role = relateTo(movie, Role.class, "ACTS_IN");
        role.setTitle(title);
        return role;
    }
}

20.6. Indexing

The Neo4j graph database can use different so-called 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.

20.6.1. Exact and numeric index

When using the standard Neo4j API, nodes and relationships have to be manually indexed with key-value pairs, typically being the property name and value. When using Spring Data Neo4j, this task is simplified to just adding an @Indexed annotation on entity fields by which the entity should be searchable. This will result in automatic updates of the index every time an indexed field changes.

Numerical fields are indexed numerically so that they are available for range queries. All other fields are indexed with their string representation.

The @Indexed annotation also provides the option of using a custom index. 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.

The indexes can be queried by using a repository (see Section 20.8, “CRUD with repositories”). Typically, the repository is an instance of org.springframework.data.neo4j.repository.DirectGraphRepositoryFactory. The methods findByPropertyValue() and findAllByPropertyValue() work on the exact indexes and return the first or all matches. To do range queries, use findAllByRange() (please note that currently both values are inclusive).

Example 20.9. Indexing entities

@NodeEntity
class Person {
    @Indexed(indexName = "people") String name;
    @Indexed int age;
}

GraphRepository<Person> graphRepository = template.repositoryFor(Person.class);

// Exact match, in named index
Person mark = graphRepository.findByPropertyValue("people", "name", "mark");

// Numeric range query, index name inferred automatically
for (Person middleAgedDeveloper : graphRepository.findAllByRange("age", 20, 40)) {
    Developer developer=middleAgedDeveloper.projectTo(Developer.class);
}

20.6.2. Fulltext indexes

Spring Data Neo4j also supports fulltext indexes. By default, indexed fields are stored in an exact lookup index. To have them analyzed and prepared for fulltext search, the @Indexed annotation has the boolean fulltext attribute. Please note that fulltext indexes require a separate index name as the fulltext configuration is stored in the index itself.

Access to the fulltext index is provided by the findAllByQuery() repository method. Wildcards like * are allowed. Generally though, the fulltext querying rules of the underlying index provider apply. See the Lucene documentation for more information on this.

Example 20.10. Fulltext indexing

@NodeEntity
class Person {
    @Indexed(indexName = "people-search", type=FULLTEXT) String name;
}

GraphRepository<Person> graphRepository =
             template.repositoryFor(Person.class);

Person mark = graphRepository.findAllByQuery("people-search", "name", "ma*");


Note

Please note that indexes are currently created on demand, so whenever an index that doesn't exist is requested from a query or get operation it is created. This is subject to change but has currently the implication that those indexes won't be configured as fulltext which causes subsequent fulltext updates to those indexes to fail.

20.6.3. Manual index access

The index for a domain class is also available from GraphDatabaseContext via the getIndex() method. The second parameter is optional and takes the index name if it should not be inferred from the class name. It returns the index implementation that is provided by Neo4j.

Example 20.11. Manual index usage

@Autowired GraphDatabaseContext gdc;

// Default index
Index<Node> personIndex = gdc.getIndex(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");
namedPersonIndex.get("name", "Mark");

// Fulltext index
Index<Node> personFulltextIndex = gdc.getIndex(Person.class, "people-search", true);
personFulltextIndex.query("name", "*cha*");
personFulltextIndex.query("{name:*cha*}");

20.6.4. Indexing in Neo4jTemplate

Neo4jTemplate also offers index support, providing auto-indexing for fields at creation time. There is an autoIndex method that can also add indexes for a set of fields in one go.

For querying the index, the template offers query methods that take either the exact match parameters or a query object/expression, and push the results wrapped uniformly as Paths to the supplied PathMapper to be converted or collected.

20.7. Neo4jTemplate

The Neo4jTemplate offers the convenient API of Spring templates for the Neo4j graph database.

20.7.1. Basic operations

For direct retrieval of nodes and relationships, the getReferenceNode(), getNode() and getRelationship() methods can be used.

There are methods (createNode() and createRelationship()) for creating nodes and relationships that automatically set provided properties.

Example 20.12. Neo4j template

        // TODO auto-post-construct !!
        Neo4jOperations neo = new Neo4jTemplate(graphDatabase).postConstruct();

        Node mark = neo.createNode(map("name", "Mark"));
        Node thomas = neo.createNode(map("name", "Thomas"));

        neo.createRelationshipBetween(mark, thomas, "WORKS_WITH", map("project", "spring-data"));

        neo.index("devs", thomas, "name", "Thomas");
        // Cypher TODO
        assertEquals( "Mark", neo.query("start p=node({person}) match p<-[:WORKS_WITH]-other return other.name",
                                  map("person", asList(thomas.getId()))).to(String.class).single());



        // Gremlin
        assertEquals(thomas, neo.execute("g.v(person).out('WORKS_WITH')",
                map("person", mark.getId())).to(Node.class).single());

        // Index lookup
        assertEquals(thomas, neo.lookup("devs", "name", "Thomas").to(Node.class).single());

        // Index lookup with Result Converter
        assertEquals("Thomas", neo.lookup("devs", "name", "Thomas").to(String.class, new ResultConverter.ResultConverterAdapter<PropertyContainer, String>() {
            public String convert(PropertyContainer element, Class<String> type) {
                return (String) element.getProperty("name");
            }
        }).single());

20.7.2. Result

All querying methods of the template return a uniform result type: Result<T> which is also an Iterable<T>. The query result offers methods of converting each element to a target type result.to(Type.class) optionally supplying a ResultConverter<FROM,TO> which takes care of custom conversions. By default most query methods can already handle conversions from and to: Paths, Nodes, Relationship and GraphEntities as well as conversions backed by registered ConversionServices. A converted Result<FROM> is an Iterable<TO>. Results can be limited to a single value using the result.single() method. It also offers support for a pure callback function using a Handler<T>.

20.7.3. Indexing

Adding nodes and relationships to an index is done with the index() method.

The lookup() methods either take a field/value combination to look for exact matches in the index, or a Lucene query object or string to handle more complex queries. All lookup() methods return a Result<PropertyContainer> to be used or transformed.

20.7.4. Graph traversal

The traversal methods are at the core of graph operations. The traverse() method covers the full traversal operation that takes a TraversalDescription (typically built with the Traversal.description() DSL) and runs it from the given start node. traverse returns a Result<Path> to be used or transformed.

20.7.5. Cypher Queries

The Neo4jTemplate also allows execution of arbitrary Cypher queries. Via the query methods the statement and parameter-Map are provided. Cypher Queries return tabular results, so the Result<Map<String,Object>> contains the rows which can be either used as they are or converted as needed.

20.7.6. Gremlin Scripts

Gremlin Scripts can run with the execute method, which also takes the parameters that will be available as variables inside the script. The result of the executions is a generic Result<Object> fit for conversion or usage.

20.7.7. Transactions

The Neo4jTemplate provides configurable implicit transactions for all its methods. By default it creates a transaction for each call (which is a no-op if there is already a transaction running). If you call the constructor with the useExplicitTransactions parameter set to true, it won't create any transactions so you have to provide them using @Transactional or the TransactionTemplate.

20.7.8. Neo4j REST Server

If the template is configured to use a RestGraphDatabase the expensive operations 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.

20.8. CRUD with repositories

The repositories provided by Spring Data Neo4j build on the composable repository infrastructure in Spring Data Commons. They allow for interface based composition of repositories consisting of provided default implementations for certain interfaces and additional custom implementations for other methods.

Spring Data Neo4j repositories support annotated and named queries for the Neo4j Cypher query-language.

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 and implementations. CRUDRepository provides basic operations, IndexRepository and NamedIndexRepository delegate to Neo4j's internal indexing subsystem for queries, and TraversalRepository handles Neo4j traversals.

GraphRepository is a convenience repository interface, extending CRUDRepository, IndexRepository, and TraversalRepository. Generally, it has all the desired repository methods. If named index operations are required, then NamedIndexRepository may also be included.

20.8.1. CRUDRepository

CRUDRepository delegates to the configured TypeRepresentationStrategy (see Section 20.14, “Entity type representation”) for type based queries.

Load an instance via a Neo4j node id

T findOne(id)

Check for existence of a Neo4j node id

boolean exists(id)

Iterate over all nodes of a node entity type

Iterable<T> findAll() (supported in future versions: Iterable<T> findAll(Sort) and Page<T> findAll(Pageable))

Count the instances of a node entity type

Long count()

Save a graph entity

T save(T) and Iterable<T> save(Iterable<T>)

Delete a graph entity

void delete(T), void; delete(Iterable<T>), and deleteAll()

Important to note here is that the save, delete, and deleteAll methods are only there to conform to the org.springframework.data.repository.Repository interface. The recommended way of saving and deleting entities is by using entity.persist() and entity.remove().

20.8.2. IndexRepository and NamedIndexRepository

IndexRepository 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 @Indexed annotation.

Iterate over all indexed entity instances with a certain field value

Iterable<T> findAllByPropertyValue(key, value)

Get a single entity instance with a certain field value

T findByPropertyValue(key, value)

Iterate over all indexed entity instances with field values in a certain numerical range (inclusive)

Iterable<T> findAllByRange(key, from, to)

Iterate over all indexed entity instances with field values matching the given fulltext string or QueryContext query

Iterable<T> findAllByQuery(key, queryOrQueryContext)

There is also a NamedIndexRepository with the same methods, but with an additional index name parameter, making it possible to query any index.

20.8.3. TraversalRepository

TraversalRepository delegates to the Neo4j traversal framework.

Iterate over a traversal result

Iterable<T> findAllByTraversal(startEntity, traversalDescription)

20.8.4. Cypher-Queries

20.8.4.1. Annotated Queries

Queries for the cypher graph-query language can be supplied with the @Query annotation. That means every method annotated with @Query("start n=(%node) match (n)-->(m) return m") will use the query string. The named parameter %node will be replaced by the actual method parameters. Node and Relationship-Entities are resolved to their respective id's and all other parameters are replaced directly (i.e. Strings, Longs, etc). There is special support for the Sort and Pageable parameters from Spring Data Commons, which are supported to add programmatic paging and sorting (alternatively static paging and sorting can be supplied in the query string itself). For using the named parameters you have to either annotate the parameters of the method with the @Param("node") annotation or enable debug symbols.

20.8.4.2. Named Queries

Spring Data Neo4j also supports the notion of named queries which are externalized in property-config-files (META-INF/neo4j-named-queries.properties). Those files have the format: Entity.finderName=query (e.g. Person.findBoss=start p=({p_person}) match (p)<-[:BOSS]-(boss) return boss). Otherwise named queries support the same parameters as annotated queries. For using the named parameters you have to either annotate the parameters of the method with the @Param("p_person") annotation or enable debug symbols.

20.8.4.3. Query results

Typical results for queries are Iterable<Type>, Iterable<Map<String,Object>>, Type and Page<Type>. Nodes and Relationships are converted to their respective Entities (if they exist). Other values are converted using the registered Spring conversion services (e.g. enums).

20.8.4.4. Cypher Examples

There is a screencast available showing many features of the query language. The following examples are taken from the cineasts dataset of the tutorial section.

start n=(0) return n

returns the node with id 0

start movie=(Movie,title,'Matrix') return movie

returns the nodes which are indexed as 'Matrix'

start movie=(Movie,title,'Matrix') match (movie)<-[:ACTS_IN]-(actor) return actor.name

returns the names of the actors that have a ACTS_IN relationship to the movie node for matrix

start movie=(Movie,title,'Matrix') match (movie)<-[r,:RATED]-(user) where r.stars > 3 return user.name, r.stars, r.comment

returns users names and their ratings (>3) of the movie matrix

start user=(User,login,'micha') match (user)-[:FRIEND]-(friend)-[r,:RATED]->(movie) return movie.title, AVG(r.stars), count(*) order by AVG(r.stars) desc, count(*) desc

returns the movies rate by the friends of the user 'micha', aggregated by movie.title, with averaged ratings and rating-counts sorted by both

20.8.4.5. Derived Finder Methods

Use the meta information of your domain model classes to declare repository finders that navigate along relationships and compare properties. The path defined with the method name is used to create a Cypher query that is executed on the graph.

Example 20.13. Repository and usage of derived finder methods

    @NodeEntity
    public static class Person {
        @GraphId Long id;
        private String name;
        private Group group;

        private Person(){}
        public Person(String name) {
            this.name = name;
        }
    }
    @NodeEntity
    public static class Group {
        @GraphId Long id;
        private String title;
        // incoming relationship for the person -> group
        @RelatedTo(type = "group", direction = Direction.INCOMING)
        private Set<Person> members=new HashSet<Person>();

        private Group(){}
        public Group(String title, Person...people) {
            this.title = title;
            members.addAll(asList(people));
        }
    }
    public interface PersonRepository extends GraphRepository<Person> {
        Iterable<Person> findByGroupTitle(String name);
    }

    @Autowired PersonRepository personRepository;

        Person oliver=personRepository.save(new Person("Oliver"));
        final Group springData = new Group("spring-data",oliver);
        groupRepository.save(springData);

        final Iterable<Person> members = personRepository.findByGroupTitle("spring-data");
        assertThat(members.iterator().next().name, is(oliver.name));

20.8.5. Creating repositories

The Repository instances are either created manually via a DirectGraphRepositoryFactory, bound to a concrete node or relationship entity class. The DirectGraphRepositoryFactory is configured in the Spring context and can be injected.

Example 20.14. Using GraphRepositories

GraphRepository<Person> graphRepository = template
        .repositoryFor(Person.class);

Person michael = graphRepository.save(new Person("Michael", 36));

Person dave = graphRepository.findOne(123);

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

20.8.6. Composing repositories

The recommended way of providing repositories is to define a repository interface per domain class. The mechanisms provided by the repository infrastructure will automatically detect them, along with additional implementation classes, and create an injectable repository implementation to be used in services or other spring beans.

Example 20.15. Composing repositories

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 {}

// provide a custom extension if needed
public interface PersonRepositoryExtension {
    Iterable<Person> findFriends(Person person);
}

public class PersonRepositoryImpl implements PersonRepositoryExtension {
    // optionally inject default repository, or use DirectGraphRepositoryFactory
    @Autowired PersonRepository baseRepository;
    public Iterable<Person> findFriends(Person person) {
        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.findAllByProperyValue("occupation","developer");

Iterable<Person> aTeam = graphRepository.findAllByQuery( "name","A*");

Iterable<Person> friends = personRepository.findFriends(dave);

20.9. Projecting entities

As the underlying data model of a graph database doesn't imply and enforce strict type constraints like a relational model does, it offers much more flexibility on how to model your domain classes and which of those to use in different contexts.

For instance an order can be used in these contexts: customer, procurement, logistics, billing, fulfillment and many more. Each of those contexts requires its distinct set of attributes and operations. As Java doesn't support mixins one would put the sum of all of those into the entity class and thereby making it very big, brittle and hard to understand. Being able to take a basic order and project it to a different (not related in the inheritance hierarchy or even an interface) order type that is valid in the current context and only offers the attributes and methods needed here would be very benefitial.

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.

This could for instance also be used to handle nodes of a traversal with a unified (simpler) type (e.g. for reporting or auditing) and only project them to a concrete, more functional target type when the business logic requires it.

Example 20.16. Projection of entities

@NodeEntity
class Trainee {
    String name;
    @RelatedTo
    Set<Training> trainings;
}

for (Person person : graphRepository.findAllByProperyValue("occupation","developer")) {
    Developer developer = person.projectTo(Developer.class);
    if (developer.isJavaDeveloper()) {
        trainInSpringData(developer.projectTo(Trainee.class));
    }
}

20.10. Geospatial Queries

SpatialRepository is a dedicated Repository for spatial queries. Spring Data Neo4j provides an optional dependency to neo4j-spatial which is an advanced library for gis operations. So if you include the maven dependency in your pom.xml, Neo4j-Spatial and the required SPATIAL index provider is available.

Example 20.17. Neo4j-Spatial Dependencies

<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-spatial</artifactId>
    <version>0.7-SNAPSHOT</version>
</dependency>


For having your entities available for spatial index queries, please include a String property containing a "well known text", location string. WKT is the Well Known Text Spatial Format eg. POINT( LON LAT ) or POLYGON (( LON1 LAT1 LON2 LAT2 LON3 LAT3 LON1 LAT1 ))

Example 20.18. Fields of

@NodeEntity
class Venue {
   String name;
   @Indexed(type = POINT, indexName = "...") String wkt;
   public void setLocation(float lon, float lat) {
      this.wkt = String.format("POINT( %.2f %.2f )",lon,lat);
   }
}

venue.setLocation(56,15);


After adding the SpatialRepository to your repository you can use the findWithinBoundingBox, findWithinDistance, findWithinWellKnownText.

Example 20.19. Spatial Queries

    Iterable<Person> teamMembers = personRepository.findWithinBoundingBox("personLayer", 55, 15, 57, 17);
Iterable<Person> teamMembers = personRepository.findWithinWellKnownText("personLayer", "POLYGON ((15 55, 15 57, 17 57, 17 55, 15 55))");
Iterable<Person> teamMembers = personRepository.findWithinDistance("personLayer", 16,56,70);


Example 20.20. Methods of the Spatial Repository

public interface SpatialRepository<T> {
    ClosableIterable<T> findWithinBoundingBox(String indexName, double lowerLeftLat,
                                              double lowerLeftLon,
                                              double upperRightLat,
                                              double upperRightLon);

    ClosableIterable<T> findWithinDistance( final String indexName, final double lat, double lon, double distanceKm);

    ClosableIterable<T> findWithinWellKnownText( final String indexName, String wellKnownText);
}


20.11. Introduced methods

The node and relationship aspects introduce (via AspectJ ITD - inter type declaration) several methods to the entities.

Persisting the node entity after creation and after changes outside of a transaction. Participates in an open transaction, or creates its own implicit transaction otherwise.

nodeEntity.persist()

Accessing node and relationship IDs

nodeEntity.getNodeId() and relationshipEntity.getRelationshipId()

Accessing the node or relationship backing the entity

entity.getPersistentState()

equals() and hashCode() are delegated to the underlying state

entity.equals() and entity.hashCode()

Creating relationships to a target node entity, and returning the relationship entity instance

nodeEntity.relateTo(targetEntity, relationshipClass, relationshipType)

Retrieving a single relationship entity

nodeEntity.getRelationshipTo(targetEntity, relationshipClass, relationshipType)

Creating relationships to a target node entity and returning the relationship

nodeEntity.relateTo(targetEntity, relationshipType)

Retrieving a single relationship

nodeEntity.getRelationshipTo(targetEnttiy, relationshipType)

Removing a single relationship

nodeEntity.removeRelationshipTo(targetEntity, relationshipType)

Remove the node entity, its relationships, and all index entries for it

nodeEntity.remove() and relationshipEntity.remove()

Project entity to a different target type, using the same backing state

entity.projectTo(targetClass)

Traverse, starting from the current node. Returns end nodes of traversal converted to the provided type.

nodeEntity.findAllByTraversal(targetType, traversalDescription)

Traverse, starting from the current node. Returns EntityPaths of the traversal result bound to the provided start and end-node-entity types

Iterable<EntityPath> findAllPathsByTraversal(traversalDescription)

Executes the given query, providing the {self} variable with the node-id and returning the results converted to the target type.

<T> Iterable<T> NodeBacked.findAllByQuery(final String query, final Class<T> targetType)

Executes the given query, providing {self} variable with the node-id and returning the original result, but with nodes and relationships replaced by their appropriate entities.

Iterable<Map<String,Object>> NodeBacked.findAllByQuery(final String query)

Executes the given query, providing {self} variable with the node-id and returns a single result converted to the target type.

<T> T NodeBacked.findByQuery(final String query, final Class<T> targetType)

20.12. Transactions

Neo4j is a transactional database, only allowing modifications to be performed within transaction boundaries. Reading data does however not require transactions.

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 use Neo4j's transaction manager.

Note

The explicit XML configuration given below is encoded in the Neo4jConfiguration configuration bean that uses Spring's @Configuration feature. This greatly simplifies the configuration of Spring Data Neo4j.

Example 20.21. Simple transaction manager configuration

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager">
        <bean class="org.neo4j.kernel.impl.transaction.SpringTransactionManager">
            <constructor-arg ref="graphDatabaseService"/>
        </bean>
    </property>
    <property name="userTransaction">
        <bean class="org.neo4j.kernel.impl.transaction.UserTransactionImpl">
            <constructor-arg ref="graphDatabaseService"/>
        </bean>
    </property>
</bean>

<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>

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

Example 20.22. Neo4j Spring integration

<![CDATA[<context:annotation-config />
<context:spring-configured/>

<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">
    <constructor-arg value="target/test-db"/>
    <constructor-arg>
        <map>
            <entry key="tx_manager_impl" value="spring-jta"/>
        </map>
    </constructor-arg>
</bean>

<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>

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, use ChainedTransactionManager, which comes bundled with Spring Data Neo4j. It takes a list of transaction managers as constructor params and will handle them in order for transaction start and commit (or rollback) in the reverse order.

Example 20.23. ChainedTransactionManager example

<![CDATA[<bean id="jpaTransactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="jtaTransactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager">
        <bean class="org.neo4j.kernel.impl.transaction.SpringTransactionManager">
            <constructor-arg ref="graphDatabaseService" />
        </bean>
    </property>
    <property name="userTransaction">
        <bean  class="org.neo4j.kernel.impl.transaction.UserTransactionImpl">
            <constructor-arg ref="graphDatabaseService" />
        </bean>
    </property>
</bean>
<bean id="transactionManager"
        class="org.springframework.data.neo4j.transaction.ChainedTransactionManager">
    <constructor-arg>
        <list>
            <ref bean="jpaTransactionManager"/>
            <ref bean="jtaTransactionManager"/>
        </list>
    </constructor-arg>
</bean>

<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>

20.13. Detached node entities

Node entities can be in two different persistence state: attached or detached. By default, newly created node entities are in the detached state. When persist() is called on the entity, it becomes attached to the graph, and its properties and relationships are stores in the database. If persist() is not called within a transaction, it automatically creates an implicit transaction for the operation.

Changing an attached entity inside a transaction will immediately write through the changes to the datastore. Whenever an entity is changed outside of a transaction it becomes detached. The changes are stored in the entity itself until the next call to persist().

All entities returned by library functions are initially in an attached state. Just as with any other entity, changing them outside of a transaction detaches them, and they must be reattached with persist() for the data to be saved.

Example 20.24. Persisting entities

@NodeEntity
class Person {
    String name;
    Person(String name) { this.name = name; }
}

// Store Michael in the database.
Person p = new Person("Michael").persist();

20.13.1. Relating detached entities

As mentioned above, an entity simply created with the new keyword starts out detached. It also has no state assigned to it. If you create a new entity with new and then throw it away, the database won't be touched at all.

Now consider this scenario:

Example 20.25. Relationships outside of transactions

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


Neither the actor nor the movie has been assigned a node in the graph. If we were to call movie.persist(), then Spring Data Neo4j 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.

Important to note here is that if actor.persist() 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.

The persist 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 as long as they contain distinct changes. For concurrent changes a concurrent modification exception is thrown (subject to be parametrizable in the future).

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 persist() is however only cascaded to related entity fields that have been modified.

In the following example, the actor and the movie are both attached entites, having both been previously persisted to the graph:

Example 20.26. Cascade for modified fields

actor.setName("Billy Bob");
movie.persist();


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 movie.persist(). The reason for this is, as mentioned above, that cascading will only be done for fields that have been modified. Since the movie.topActor field has not been modified, it will not cascade the persist operation to the actor.

20.14. Entity type representation

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 this type information is saved in the graph database.

Implementations of TypeRepresentationStrategy take care of persisting this information on entity instance creation. They also provide the repository methods that use this type information to perform their operations, like findAll and count.

There are three available implementations for node entities to choose from.

  • IndexingNodeTypeRepresentationStrategy

    Stores entity types in the integrated index. Each entity node gets indexed with its type and any supertypes that are also@NodeEntity-annotated. The special index used for this is called__types__. Additionally, in order to get the type of an entity node, each node has a property __type__ with the type of that entity.

  • SubReferenceNodeTypeRepresentationStrategy

    Stores entity types in a tree in the graph representing the type hierarchy. Each entity has a INSTANCE_OF relationship to a type node representing that entity's type. The type may or may not have a SUBCLASS_OF relationship to another type node.

  • NoopNodeTypeRepresentationStrategy

    Does not store any type information, and does hence not support finding by type, counting by type, or retrieving the type of any entity.

There are two implementations for relationship entities available, same behavior as the corresponding ones above:

  • IndexingRelationshipTypeRepresentationStrategy

  • NoopRelationshipTypeRepresentationStrategy

Spring Data Neo4j will by default autodetect which are the most suitable strategies for node and relationship entities. For new data stores, it will always opt for the indexing strategies. If a data store was created with the olderSubReferenceNodeTypeRepresentationStrategy, then it will continue to use that strategy for node entities. It will however in that case use the no-op strategy for relationship entities, which means that the old data stores have no support for searching for relationship entities. The indexing strategies are recommended for all new users.

20.15. Bean validation (JSR-303)

Spring Data Neo4j supports property-based validation support. When a property is changed, it is checked against the annotated constraints, e.g. @Min, @Max, @Size, etc. Validation errors throw a ValidationException. The validation support that comes with Spring is used for evaluating the constraints. To use this feature, a validator has to be registered with the GraphDatabaseContext.

Example 20.27. Bean validation

@NodeEntity
class Person {
    @Size(min = 3, max = 20)
    String name;

    @Min(0) @Max(100)
    int age;
}