Chapter 19. Programming model for Spring Data Graph

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

19.1. Overview of the AspectJ support

Behind the scenes Spring Data Graph leverages AspectJ (Chapter 25, AspectJ introduction) aspects to modify the behavior of simple POJO entities to be able to be backed by a graph store. Each node entity is backed by a graph node that holds its properties and relationships to other entities. AspectJ is used to intercept field access and to retrieve the information from the backing node (either its properties or relationships or dynamic traversals starting from the node). For relationship entities the fields are similarly mapped to properties. There are two specially annotated fields for the start and the end node of the relationship.

The aspect introduces some internal fields and some public methods (Section 19.9, “Methods added to entity classes”) to the entities for accessing the backing state via getPersistentState() and creating relationships with relateTo and retrieving relationship entities via getRelationshipTo. It also introduces graphRepository methods like find(Class<? extends NodeEntity>, TraversalDescription) and equals and hashCode delegation.

Spring Data Graph internally uses an abstraction called EntityState that the field access and instantiation advices of the aspect delegate to, keeping the aspect code very small and focused to 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.

19.1.1. IDE-AspectJ Support

As Spring Data Graph uses some advanced aspects of AspectJ, there might be issues with IDE reporting errors where there are none. Features that might be reported are: introduction of methods to interfaces, declaration of additional interfaces for annotated classes, generified introduced methods.

Eclipse and STS support AspectJ via the AJDT plugin which can be installed from the update-site: http://download.eclipse.org/tools/ajdt/36/update/ (or for the latest development snapshot of the plugin http://download.eclipse.org/tools/ajdt/36/dev/update).

The AspectJ support in IntelliJ IDEA lacks some of the features. JetBrains is working to improve the situation with the upcoming 10.5 release of the IDE (which is currently available as EAP). Building the project with Ajc works in the IDE (Options -> Compiler -> Java-Compiler should show Ajc, please add 512 MB RAM for the compiler to run).

19.2. Using annotations to define POJO Node Entities

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

19.2.1. @NodeEntity: The basic building block

The @NodeEntity 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 useShortNames is set to false, the properties and relationship names used will be prepended with the class name of the entity.

If the partial parameter is set to true, this entity takes part in a cross-store setting /Chapter 21, Cross-store persistence with a graph database) where only the specifically annotated parts of the entity not handled by JPA will be mapped to the graph store.

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

Example 19.1. Simple Node Entity

// simplest example
@NodeEntity
public class Movie {
String title;
}

19.2.2. @GraphProperty: Optional Annotation for Property Fields

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). Transient fields are not persisted. This annotation is mainly used for cross-store persistence.

19.2.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, 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 Repository for a particular node or relationship entity, created via a DirectGraphRepositoryFactory.

GraphDatabaseContext exposes the indexes for Nodes and Relationships via the getIndex method. Index names default to the domain class name, but can also be named (indexName attribute)individually to reflect domain concepts. be named, for instance to keep separate domain concepts in separate indexes.

Numerical values are indexed as such by default, allowing for range queries. Fulltext indexing is also possible by setting the fulltext attribute to true. For details see the indexing section Section 19.4, “Indexing”.

19.2.4. @GraphTraversal: fields providing direct access to traversal results

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 traversalBuilder attribute of the annotation. The class of the expected NodeEntities is provided with the elementClass attribute.

Example 19.2. @GraphTraversal in 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());
    }
}
}


19.3. How to relate Node Entities using Relationships

As relationships are first level 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 also imply its direction). They can have an arbitrary number of properties. Spring Data Graph has special support to represent Neo4j relationships as Relationship Entities but this is not mandatory.

19.3.1. @RelatedTo: Connecting NodeEntities

Every attribute of a Node Entity that refers to one or more Node Entity represents relationships and is handled by the field-aspects to be reflected in the graph.

Those can either be single relationships (1:1) or multiple relationships (1:N). In most cases single relationships to other node entities don't have to be annotated, as Spring Data Graph can extract all necessary information from the field using reflection. In the case of multiple relationships, the elementClass parameter of @RelatedTo must be specified because of type erasure. The direction (default OUTGOING) and type (inferred from field name) parameters of the annotation are optional.

Single Relationships to other node entities are created when setting the field (deleting previously set relationships) and deleted when setting it to null.

References to a set of Node Entities are declared as fields with a Set<T> type, where T is a concrete Node-Entity. @RelatedTo is used again to provide information about type-name, elementClass and direction. It is not necessary to initialize the set as it is managed by Spring Data Graph, representing the relationships from (to) this entity with the given type. Adding and removing from the set is reflected on the graph.

Spring Data Graph also ensures that there is only one relationship of the given type between two given entities.

Note

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.

Example 19.3. Node Entity with Relationships

@NodeEntity
public class Movie {
private Actor topActor;
}

@NodeEntity
public class Person {
@RelatedTo(type = "topActor", direction = Direction.INCOMING)
private Movie wasTopActorIn;
}

@NodeEntity
public class Actor {
@RelatedTo(type = "ACTS_IN", elementClass = Movie.class)
private Set<Movie> movies;
}

Other means of handling relationships are the introduced entity.getRelationshipTo(target,type) and entity.relateTo(target,type) methods that are available on each NodeEntity. Those methods create and return Neo4j relationships. It is possible to remove relationships manually using entity.removeRelationshipTo(target,type). For creating and accessing relationship-entities, their equivalents are available.

19.3.2. @RelationshipEntity: Rich relationships

To access the full data model of graph relationships, POJOs can also be annotated with @RelationshipEntity. Relationship entities can not be instantiated directly but are rather accessed via node entities, either by @RelatedToVia fields or by the introduced entity.relateTo(target,relationshipClass,type) and entity.getRelationshipTo(target,relationshipClass,type) methods (Section 19.9, “Methods added to entity classes”).

Relationship entities may contain fields that are mapped to simple properties and two special fields that are annotated with @StartNode and @EndNode which point to the start and end node entities respectively. These fields are treated as read only fields.

Example 19.4. Relationship Entity

@RelationshipEntity
public class Role {
String title;

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

19.3.3. @RelatedToVia: Connecting Node Entitites via Relationship Entities

To provide easy programmatic access to the richer relationship entities of the data model, a different annotation @RelatedToVia can be declared on fields of Iterables of the relationship entity type. These Iterables then provide read only access to instances of the entity that backs the relationship of this relationship type. Those instances are initialized with the properties of the relationship and the start and end node.

Example 19.5. Using Relationship Entities and @RelatedToVia

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

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

19.4. Indexing

The Neo4j graph database can use different index providers for exact lookups and fulltext searches. Lucene is used as default index provider implementation. There is support for distinct indexes for nodes and relationships which can be configured to be of fulltext or exact types.

19.4.1. Exact and Numeric Index

Using the standard Neo4j API, Nodes and Relationships and their indexed field-value combinations have to be added manually to the appropriate index. When using Spring Data Graph, this task is simplified by eased by applying an @Indexed annotation on entity fields. This will result in updates to the index on every change.

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 can also set the index-name to be used the default index name is the simple class name of the entity. So the same field names from different classes don't end up in the same index by default. That would return different domain objects for a single index query.

Query access to the index happens with the Node- and Relationship-Repostories that are created via an instance of org.springframework.data.graph.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).

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

    // automatically indexed numerically
    @Indexed
    int age;

}

NodeGraphRepository<Person> graphRepository = graphRepositoryFactory.createNodeEntityRepository(Person.class);

// exact graphRepository
Person mark = graphRepository.findByProperyValue("people","name","mark");

// numeric range queries
for (Person middleAgedDeveloper : graphRepository.findAllByRange(null, "age", 20, 40)) {
    Developer developer=middleAgedDeveloper.projectTo(Developer.class);
}

19.4.2. Fulltext Indexes

Spring Data Graph also supports full-text 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 method of the repositories. Wildcard like * are allowed. Otherwise the fulltext querying rules of the underlying index provider apply. (In most cases this will be lucene.

@NodeEntity
class Person {
    @Indexed(indexName = "person-name", fulltext=true)
    String name;
}

NodeGraphRepository<Person> graphRepository = graphRepositoryFactory.createNodeEntityRepository(Person.class);

// exact graphRepository
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.

19.4.3. Raw Index Access

The raw 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 doesn't default to the simple domain class name. It returns the Index implementation that is provided by Neo4j.

@Autowired GraphDatabaseContext gdc;

// exact index
Index<Node> personIndex=gdc.getIndex(Person.class,null);
personIndex.add(node,"name","Mark");

Index<Node> namedPersonIndex=gdc.getIndex(Person.class,"people");
namedPersonIndex.get("name","Mark");

// complex range & sort query
namedPersonIndex.query( new QueryContext( NumericRangeQuery.newÍntRange( "age", 20, 40, true, true ) )
                        .sort( new Sort( new SortField( "age", SortField.INT, false ) ) ) );

// fulltext index
Index<Node> personFulltextIndex=gdc.getIndex(Person.class,"person-name",true);
namedPersonIndex.query("name","Ma*");
namedPersonIndex.query("{name:Ma*}");

            

19.4.4. Indexing in Neo4jTemplate

Neo4jTemplate also offers index support, providing auto-indexing for fields at creation time of nodes and relationships. 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 / query expression and push the results wrapped uniformly as Paths to the supplied PathMapper to be converted or collected.

19.5. GraphRepositories for basic CRUD and find-operations

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

Note

Spring Data Graph provides only the infrastructure and some default repository implementations so far. In future releases support for finders derived from method names, named queries and annotated query methods will be added. (e.g. findByName(name), @Query(name = "find-by-name-query") findByName(name), @Query(query = "{name:%s}") findByName(name))

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 and implementations. One CRUD-Repository (CRUDGraphRepository<T>) that provides basic operations, a IndexQueryExecutor that delegates to Neo4j's internal indexing subsystem for executing queries. And last but not least a TraversalQueryExecutor that handles Neo4J Traversals.

CRUDGraphRepository delegates to the configured TypeRepresentationStrategy (Section 19.8, “Storing type information in the graph”) for type based queries.

loading an instance via the Neo4j node id

T findOne(id)

checks for existence via the Neo4j node id

boolean exists(id)

iterating over all nodes of a node entity type

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

counting the instances of a node entity type

Long count()

saves the graph entities

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

deletes the graph entities

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

IndexQueryExecutor works with the indexing subsystem and provides methods to find entities by indexed properties, ranged queries of combination thereof.

iterating over all indexed instances with a certain property value

Iterable<T> findAllByPropertyValue(indexName, keyName, value)

getting a single instance with a certain property value

T findByPropertyValue(indexName, keyName, value)

iterating over all indexed instances within a certain numerical range (inclusive)

Iterable<T> findAllByRange(indexName, keyName, from, to)

iterating over all indexed instances matching the given fulltext (or QueryContext query)

Iterable<T> findAllByQuery(indexName, keyName, queryOrQueryContext)

TraversalQueryExecutor works with the traversal framework.

iterating over a traversal result

Iterable<T> findAllByTraversal(startNode, traversalDescription)

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

Example 19.6. Using GraphRepositories

NodeGraphRepository<Person> graphRepository = graphRepositoryFactory.createNodeEntityRepository(Person.class);

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

Person dave=graphRepository.findOne(123);

Long numberOfPeople = graphRepository.count();

Person mark = graphRepository.findByPropertyValue(null,"name", "mark");

Iterable<Person> devs = graphRepository.findAllByProperyValue(null, "occupation","developer");

Iterable<Person> middleAgedPeople = graphRepository.findAllByRange(null, "age",20,40);

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

Iterable<Person> davesFriends = graphRepository.findAllByTraversal(dave,
    Traversal.description().pruneAfterDepth(1)
    .relationships(KNOWS).filter(returnAllButStartNode()));


19.5.1. Composing Repositories

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

Example 19.7. Composing Repositories

public interface PersonRepository extends NodeGraphRepository<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 (graphDatabaseContext reference is optional)
<datagraph:repositories base-package="org.springframework.data.graph.neo4j" graph-database-context-ref="graphDatabaseContext"/>

// have it injected
@Autowired
PersonRepository personRepository;

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

    Person dave=personRepository.findOne(123);

    Iterable<Person> devs = personRepository.findAllByProperyValue(null, "occupation","developer");

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

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


19.6. Transactions in Spring Data Graph

Neo4j is a transactional datastore which only allows modifications within transaction boundaries and fullfills the ACID properties. Reading from the store is also possible outside of transactions.

Spring Data Graph 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. Note: The explicit XML configuration given below is encoded in the Neo4jConfiguration configuration bean that uses Spring's @Configuration functioanlity. This simplifies the configuration. An example is shown further below.

<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 running multiple transactional resources there are two options. First of all you can have Neo4j participate in the externally set up transaction manager using the new SpringProvider by enabling the configuration parameter for your graph database. Either via the spring config or the configuration file (neo4j.properties).

<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.graph.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"/>
                

You can configure a stock XA transaction manager to be used with Neo4j and the other resources (e.g. Atomikos, JOTM, App-Server-TM). For a bit less secure but fast 1 phase commit best effort, use the implementation coming with Spring Data Graph (ChainedTransactionManager). 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.

<bean id="transactionManager"
        class="org.springframework.data.graph.neo4j.transaction.ChainedTransactionManager" >
    <constructor-arg>
        <list>
        <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="jpaTransactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
        </bean>
        <bean
            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>
        </list>
    </constructor-arg>
</bean>

19.7. Session handling - attached and detached entities

By default newly created node entities are in a detached state. When persist() 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.

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 persist() them again.

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.

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.

@NodeEntity
class Person {
    String name;
}
Person p = new Person().persist();

19.8. Storing type information in the graph

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

19.9. Methods added to entity classes

The node and relationship aspects introduce (via ITD - inter type declaration) several methods to the entities that make common tasks easier.

persisting the node-entity initially and after changes outside of a transaction, persist participates in a transaction or creates its own implict transaction.

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(targetEnttiy, 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 index entries

entity.remove()

projecting to a different target type

entity.projectTo(targetClass)

traversing, starting at the current node, returns end-nodes of traversal converted to provided type

nodeEntity.findAllByTraversal(targetType, traversalDescription)

traversing, starting at the current node, returns EntityPath's of the traversal result bound to the provided start and end-node-entity types

Iterable<EntityPath> findAllPathsByTraversal(traversalDescription)

19.10. Dynamic typing - Projection to unrelated, fitting types

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

// not related to Person at all
@NodeEntity
class Trainee {
    String name;
    @RelatedTo(elementClass=Training.class);
    Set<Training> trainings;
}

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

19.11. Bean Validation - JSR-303

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

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

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