Spring Data Neo4j Version 4-M1 (Feb 2015)
+© 2010-2015 Neo Technology, Inc. - Pivotal Software, Inc.
+|
+ Note
+ |
++Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee +for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. + | +
-
+
- Preface + + +
- Spring Data Neo4j 4 Reference Documentation
+
-
+
- About the Spring Data project
+
-
+
- About SDN 4 +
+ - Overview + + +
- Getting started + + +
- Programming model +
- 1. Under the hood + + +
- 2. Simplified Object-Graph Mapping +
- 3. Defining node entities + + +
- 4. Relating node entities + + +
- 5. Indexing + + +
- 6. Neo4jTemplate + + +
- 7. CRUD with repositories + + +
- 8. Conversion + + +
- 9. Transactions +
- 10. Entity Attachment + + +
- 11. Entity Type Representation + + +
- 12. Bean Validation (JSR-303) +
- Performance considerations + + +
+ - About the Spring Data project
+
- Migrating from previous versions of Spring Data Neo4j + + +
Preface
+Foreword
+++++I’m excited about Spring Data Neo4j for several reasons.
+++First, this project is in a very important space. We are in an era of transition. A very few years ago, a relational database was a given for storing nearly all the data in nearly all applications. While relational databases remain important, new application requirements and massive data proliferation have prompted a richer choice of data stores. Graph databases have some very interesting strengths, and Neo4j is proving itself valuable in many applications. It’s a choice you should add to your toolbox.
+++Second, Spring Data Neo4j is an innovative project, which makes it easy to work with one of the most interesting new data stores. Unfortunately, the proliferation of new data stores has not been matched by innovation in programming models to work with them. Ironically, just after modern ORM mapping made working with relational data in Java relatively easy, the data store disruption occurred, and developers were back to square one: struggling once more with clumsy, low level APIs. Working with most non-relational technologies is overly complex and imposes too much work on developers. Spring Data Neo4j makes working with Neo4j amazingly easy, and therefore has the potential to make you more successful as a developer. Its use of AspectJ to eliminate persistence code from your domain model is truly innovative, and on the cutting edge of today’s Java technologies.
+++Third, I’m excited about Spring Data Neo4j for personal reasons. I no longer get to write code as often as I would like. My initial convictions that Spring and AspectJ could both make building applications with Neo4j dramatically easier and cross-store object navigation possible gave me an excuse for a much-needed coding binge early in 2010. This led to a prototype of what became Spring Data Neo4j — at times written paired with Emil. I’m sure the vast majority of my code has long since been replaced (probably for the better) by coders who aren’t rusty — thanks Michael and Thomas! — but I retain my pleasant memories.
+++Finally, Spring Data Neo4j is part of the broader Spring Data project: one of the key areas in which Spring is innovating to help meet new application requirements. I encourage you to explore Spring Data, and — better still — become involved in the community and contribute.
+++Enjoy the Spring Data Neo4j book, and happy coding!
+
+Founder of the Spring Framework +
++++"Spring is the most popular middleware on the planet," I thought to myself as I walked up to Rod Johnson in late 2009 at the JAOO conference in Aarhus, Denmark. Rod had just given an introductory presentation about Spring Roo and when he was done I told him "Great talk. You’re clearly building a stack for the future. What about support for non-relational databases?"
+++We started talking and quickly agreed that NOSQL will play an important role in emerging stacks. Now, a year and half later, Spring Data Neo4j is available in its first stable release and I’m blown away by the result. Never before in any environment, in any programming framework, in any stack, has it been so easy and intuitive to tap into the power of a graph database like Neo4j. It’s a testament to the efforts by an awesome team of four hackers from Neo Technology and VMware: Michael Hunger, David Montag, Thomas Risberg and Mark Pollack.
+++The Spring framework revolutionized how we all wrote enterprise Java applications and today it’s used by millions of enterprise developers. Graph databases also stand out in the NOSQL crowd when it comes to enterprise adoption. You can find graph databases used in areas as diverse as network management, fraud detection, cloud management, anything with social data, geo and location services, master data management, bioinformatics, configuration databases, and much more.
+++Spring developers deserve access to the best tools available to solve their problem. Sometimes that’s a relational database accessed through JPA. But more often than not, a graph database like Neo4j is the perfect fit for your project. I hope that Spring Data Neo4j will give you access to the power and flexibility of graph databases while retaining the familiar productivity and convenience of the Spring framework.
+++Enjoy the Spring Data Neo4j guide book and welcome to the wonderful world of graph databases!
+
+CEO of Neo Technology +
About this guide book
+The Spring Data Neo4j Project
+Welcome to the Spring Data Neo4j Guide Book. Thank you for taking the time to get an in-depth look into Spring Data Neo4j. This project is part of the Spring Data project, which brings the convenient programming model of the Spring Framework to modern NOSQL databases. Spring Data Neo4j, as the name alludes to, aims to provide support for the graph database Neo4j.
+Feedback
+It was written by developers for developers. Hopefully we’ve created a guide that is well received by our peers.
+If you have any feedback on Spring Data Neo4j or this book, please provide it via the SpringSource JIRA, the SpringSource NOSQL Forum, github comments or issues, or the Neo4j mailing list.
+Format of the Book
+This book is presented as a duplex book, a term coined by Martin Fowler. A duplex book consists of at least two parts. The first part is an easily accessible tutorial or narrative that gives the reader an overview of the topics contained in the book. It contains lots of examples and discussion topics. This part of the book is highly suited for cover-to-cover reading.
+We chose a tutorial describing the creation of a web application that allows movie enthusiasts to find their favorite movies, rate them, connect with fellow movie geeks, and enjoy social features such as recommendations. The application is running on Neo4j using Spring Data Neo4j and the well-known Spring Web Stack.
+The second part of the book is the classic reference documentation, containing detailed information about the library. It discusses the programming model, the underlying assumptions, and internals, as well as the APIs for the object-graph mapping. The reference documentation is typically used to look up concrete bits of information, or to drill down into certain topics. For hackers wanting to really delve into Spring Data Neo4j, it can of course also be read cover-to-cover.
+Acknowledgements
+We would like to thank everyone who contributed to this book, especially Mark Pollack and Thomas Risberg, the leads of the Spring Data Project, who helped a lot during the development of the library as well as sharing great feedback about the book. Also Oliver Gierke, our local German VMWare/SpringSource engineer, who invested a lot of time discussing various aspects of the library as well as providing the superb foundations for the Spring Data Repositories. We tortured Andy Clement, the AspectJ project lead, with many questions and issues around our advanced AspectJ usage which caused some headaches. He always quickly solved our issues and gave us excellent answers.
+Many thanks to our colleagues David Montag, Andreas Kollegger and Rickard Öberg who not only contributed to Spring Data Neo4j but also provided content and feedback for this book.
+We also appreciate very much the foresight of Rod Johnson and Emil Eifrem to initiate the project, and now also providing great forewords. Their leadership inspired collaboration between the engineering teams at SpringSource and Neo Technology, a tremendous help during the making of Spring Data Neo4j.
+Last but not least we thank our vibrant community, both in the Spring Forums as well as on the Neo4j Mailing list and on many other places on the internet for giving us feedback, reporting issues and suggesting improvements. Without that important feedback we wouldn’t be where we are today. Especially Jean-Pierre Bergamin and Alfredas Chmieliauskas provided exceptional feedback and contributions.
+Enjoy the book!
+Introduction to Neo4j
+What is a graph database?
+A graph database is a storage engine that is specialised in storing and retrieving vast networks of data. It efficiently +stores nodes and relationships and allows high performance traversal of those structures. Properties can be added to +nodes and relationships. Nodes can be labelled by zero or more labels, and relationships must have a direction and +exactly one type.
+Graph databases are well suited for storing most kinds of domain models. In almost all domains, there are certain things +connected to other things. In most other modeling approaches, the relationships between things are reduced to a single +link without identity and attributes. Graph databases allow to keep the rich relationships that originate from the domain +equally well-represented in the database without resorting to also modeling the relationships as "things". There is very +little "impedance mismatch" when putting real-life domains into a graph database.
+About Neo4j
+Neo4j is a NOSQL graph database. It is a fully transactional database (ACID) that stores data +structured as graphs. A graph consists of nodes, connected by relationships. Inspired by the structure of the human mind, +it allows for high query performance on complex data, while remaining intuitive and simple for the developer.
+Neo4j is very well-established. It has been in commercial development for 15 years and in production for over 12 years. Most importantly, it has a helpful and contributing community surrounding it, but it also:
+-
+
-
+
has an intuitive, rich graph-oriented model for data representation. Instead of tables, rows, and columns, you work with a graph consisting of nodes, relationships, and properties.
+
+ -
+
has a disk-based, native storage manager optimized for storing graph structures with maximum performance and scalability.
+
+ -
+
is scalable. Neo4j can handle graphs with many billions of nodes/relationships/properties on a single machine, but can also be scaled out across multiple machines for high availability.
+
+ -
+
has a powerful graph query language called Cypher, which allows users to efficiently read/write data by expressing graph patterns.
+
+ -
+
has a powerful traversal framework and query languages for traversing the graph.
+
+ -
+
can be deployed as a standalone server, which is the recommended way of using Neo4j
+
+ -
+
can be deployed as an embedded (in-process) database, giving developers access to its core Java API
+
+
In addition, Neo4j has ACID transactions, durable persistence, concurrency control, transaction recovery, high availability, and more. Neo4j is released under a dual free software/commercial licence model.
+Querying the Graph with Cypher
+Neo4j provides a graph query language called "Cypher" which +draws from many sources. It resembles SQL but with an iconic representation of patterns in the graph (concepts drawn from SPARQL). +The Cypher execution engine was written in Scala to leverage the high expressiveness for lazy sequence operations of +the language and the parser combinator library.
+Cypher queries typically begin with a MATCH clause, which can be used to provide a way to pattern match against a
+starting set of nodes, via their IDs or label-based index lookup. These starting patterns or start nodes, are then
+related to other nodes via additional MATCH clauses. Match clauses can introduce new identifiers for nodes and relationships.
+In the WHERE clause additional filtering of the result set is applied by evaluating expressions. The RETURN clause
+defines which part of the query result will be available. Aggregation also happens in the return clause by using
+aggregation functions on some of the values. Sorting can happen in the ORDER BY clause and the SKIP and LIMIT
+parts restrict the result set to a certain window.
Cypher can be executed against a Neo4j server using an HTTP request.
+// Actors who acted in a Matrix movie:
+MATCH (movie:Movie)<-[:ACTS_IN]-(actor)
+WHERE movie.title =~ 'Matrix.*'
+RETURN actor.name, actor.birthplace
+
+// User-Ratings:
+MATCH (user:User {login:'micha'})-[r:RATED]->(movie)
+WHERE r.stars > 3
+RETURN movie.title, r.stars, r.comment
+
+// Mutual Friend recommendations:
+MATCH (user:User {login:'micha'})-[:FRIEND]-(friend)-[r:RATED]->(movie)
+WHERE r.stars > 3
+RETURN friend.name, movie.title, r.stars, r.comment
+
+// Movie suggestions based on an actor:
+MATCH (movie:Movie)<-[:ACTS_IN]-()-[:ACTS_IN]->(suggestion:Movie)
+WHERE id(movie)=13
+RETURN suggestion.title, count(*) ORDER BY count(*) DESC LIMIT 5
+
+// Co-Actors, sorted by count and name of Lucy Liu
+MATCH (lucy)-[:ACTS_IN]->(movie)<-[:ACTS_IN]-(co_actor)
+WHERE lucy.name='Lucy Liu'
+RETURN count(*), co_actor.name ORDER BY count(*) DESC, co_actor.name LIMIT 20
+
+// Recommendations including counts, grouping and sorting
+MATCH (:User {login:'micha'})-[:FRIEND]-()-[r:RATED]->(movie)
+RETURN movie.title, avg(r.stars), count(*) ORDER BY avg(r.stars) DESC, count(*) DESC
+Indexing
+The best way for retrieving start nodes for traversals and queries is by using Neo4j’s integrated index facilities. +Spring Data Neo4j takes the view that index maintenance should not be part of your application code. For that reason, +it does not provide any explicit index-related functionality.
+However, it is very important that indices are in place for efficient node lookups. +Please make sure you read Neo4j Documentation +on indices.
+Spring Data Neo4j 4 Reference Documentation
+
+This part of the Spring Data Neo4j Guide book provides the reference documentation for SDN 4.
+Its content covers information about the programming model, APIs, concepts, annotations and technical details of +Spring Data Neo4j, version 4.
+Whenever you look for the means to employ the full power of the Spring Data Neo4j library, you should be able to find +your answers in the reference section. If you don’t, please inform us about missing or incorrect content so that we +can fix it.
+About the Spring Data project
+Spring Data is a SpringSource project that aims to provide Spring’s convenient +programming model and well known conventions for NOSQL databases. Currently there is support for graph (Neo4j), +key-value (Redis, Riak), document (MongoDB) and relational (Oracle) databases.
+The Spring Data Neo4j project, as part of the Spring Data initiative, aims to simplify development with the Neo4j graph +database. Like JPA, it uses annotations on simple POJO domain objects. Together with metadata, the annotations drive +mapping the POJO entities and their fields to nodes, relationships, and properties in the graph database.
+About SDN 4
+For version 4, Spring Data Neo4j has been rewritten from scratch to natively support Neo4j deployments in standalone +server mode. It uses Cypher, the Neo4j query language, and the HTTP protocol to communicate with the database. +It’s therefore worth noting that there may be some backward compatibility issues when migrating to version 4, +so be sure to check Migrating from previous versions of Spring Data Neo4j to avoid any unwanted surprises.
+For integration of Neo4j and other languages, please see Language Guides.
+Overview
+The explanation of Spring Data Neo4j’s programming model starts with some underlying details. The basic concepts of +the Object-Graph Mapping (OGM) library used by Spring Data Neo4j internally, is explained in the initial chapter.
+Getting started
+To get started with a simple application, you need only your domain model and (optionally) the annotations +(see Defining node entities) provided by the library. You use annotations to mark domain objects +to be reflected by nodes and relationships of the graph database. For individual fields the annotations allow you to +declare how they should be processed and mapped to the graph. For property fields and references to other entities this +is straightforward.
+Adding queries and computed fields
+To use advanced functionality like Cypher queries, a basic understanding of the graph data model is required. The graph +data model is explained in the chapter about Neo4j, see Introduction to Neo4j. Using computed fields that are dynamically +backed by graph operations is also possible by using the Query mechanism.
+Managing relationships
+Relationships between entities are first class citizens in a graph database and therefore worth a separate chapter +(Relating node entities) describing their usage in Spring Data Neo4j.
+Repositories
+Spring Data Commons provides a very powerful repository infrastructure that is also leveraged in Spring Data Neo4j. +Those repositories consist only of a composition of interfaces that declare the available functionality in each repository. +The implementation details of commonly used persistence methods are handled by the library. At least for typical CRUD and +query-operations that is very convenient. The repositories are extensible by annotated, named or derived finder methods. +For custom implementations of repository methods you are free to add your own code. (CRUD with repositories).
+Neo4jTemplate
+Being a Spring Data library, Spring Data Neo4j still offers a Neo4jTemplate (Neo4jTemplate)
+for interacting with the mapped entities and the Neo4j graph database if you don’t want to use repositories.
+As of version 4, Neo4jTemplate is based on the org.neo4j.ogm.session.Session object. This is the same object
+on which support for which Spring Data Neo4j Repositories is also based, so the underlying functionality is identical.
Mapping Strategies
+Because Neo4j is a schema-free database, Spring Data Neo4j uses a sophisticated mechanism to map Java types to Neo4j nodes +using labels. How that works is explained here: Entity Type Representation.
+Transactional support
+Neo4j uses transactions to guarantee the integrity of your data and Spring Data Neo4j supports this fully. The implications +of this are described in the chapter around transactions. (Transactions)
+Configuration
+As of Milestone 1, only Java bean-based configuration is supported. See Getting started for more details.
+Examples
+The provided samples, which are also publicly hosted on Github, are explained +in [reference_samples].
+Performance
+Spring Data Neo4j 4 has been rebuilt from the ground up with performance in mind. More information can be found in +Performance considerations. This chapter also discusses which use cases should not be handled with Spring Data Neo4j.
+Getting started
+Spring Data Neo4j dramatically simplifies development, but some setup is naturally required. For building the +application, Maven needs to be configured to include the Spring Data Neo4j dependencies and +after the build setup is complete, the Spring application needs to be configured to make use of Spring Data Neo4j. +Examples for these different setups can be found in the Spring Data Neo4j examples.
+Spring Data Neo4j projects can be built using Maven, Gradle or Ant/Ivy.
+Dependencies for Spring Data Neo4j
+ <dependency>
+ <groupId>org.springframework.data</groupId>
+ <artifactId>neo4j-spring</artifactId>
+ </dependency>
+Spring configuration
+Users of Spring Data Neo4j 4 can current only configure their applications using Java-based bean configuration. Support +for XML namespace configuration is expected to be available in Milestone 2
+Java-based bean configuration
+In order to configure Spring Data Neo4j with Java-based bean config, your Spring context must extend the core
+Neo4jConfiguration context that comes with Spring Data Neo4j. The example below shows how this can be done.
|
+ Note
+ |
+
+You will need to override the neo4jServer(), getSessionFactory() and getSession() bean definitions to provide
+the required context for your own application. This is explained in more detail below.
+ |
+
@Configuration
+@EnableNeo4jRepositories(basePackages = "org.neo4j.example.repository")
+@EnableTransactionManagement
+public class Application extends Neo4jConfiguration {
+
+ @Override
+ @Bean
+ public Neo4jServer neo4jServer() {
+ return new RemoteServer("http://localhost:7474");
+ }
+
+ @Override
+ @Bean
+ public SessionFactory getSessionFactory() {
+ return new SessionFactory("org.neo4j.example.domain");
+ }
+
+ @Override
+ @Bean
+ @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
+ public Session getSession() throws Exception {
+ return super.getSession();
+ }
+
+}
+Neo4jServer Bean
+The Neo4jServer interface simply provides a URL with which to access the database. The default implementations are RemoteServer and InProcessServer.
As the name implies, RemoteServer is used to provide the URL of a remote Neo4j server. This will be the most typical use case for deployed applications. As the above example shows, you simply construct a new instance with the URL of the server that you want to use.
InProcessServer is useful for test and development environments. This implementation will start a new instance of CommunityNeoServer running on an available local port and return the URL needed to connect to it. It also registers a shutdown hook so that the underlying Neo4j server exits cleanly when the JVM shuts down, via Control+C, for example.
SessionFactory Bean
+The SessionFactory is needed by SDN to create instances of org.neo4j.ogm.session.Session as required. This also sets up the object-graph mapping metadata when constructed, which is then used across all Session objects that it creates. As seen in the above example, the packages to scan for domain object metadata should be provided to the SessionFactory constructor.
Note that the session factory should typically be application-scoped. While you can use a narrower scope for this if you like, there is typically no advantage in doing so.
+Session Bean
+A Session is used to drive the object-graph mapping framework on which Spring Data Neo4j is based. All repository implementations and Neo4jTemplate are driven by the Session, and you can also auto-wire it into your Spring beans and code against it directly if you wish.
The life cycle of a Session is important to consider because it keeps track of the changes that have been made to entities and their relationships. The reason it does this is so that only entities and relationships that have changed get persisted on save, which is particularly efficient when working with large graphs. Note, however, that the Session doesn’t ever return cached objects so there’s no risk of getting stale data on load; it always hits the database.
If your application relies on long-running sessions and doesn’t reload entities then you may not see changes made from other users and find yourself working with outdated objects. On the other hand, if your sessions have too narrow a scope then your save operations can be unnecessarily expensive, as updates will be made to all objects if the session isn’t aware of the those that were originally loaded.
+There’s therefore a trade off between the two approaches. In general, the scope of a Session should correspond to a "unit of work" in your application. What this means depends on the usage scenario, but in a typical web-based Spring application we recommend using a request-scoped or HTTP-session-scoped Session. Either way, if you make sure you load fresh data at the beginning of each unit of work then data integrity shouldn’t be a problem.
Additional beans can be configured to be included in the Neo4j-Configuration just by defining them in the +Spring context in the normal way.
+Programming model
+This chapter covers the fundamentals of the programming model behind Spring Data Neo4j. +It discusses the simple and advanced mapping modes, the annotations provided by Spring Data Neo4j and how to use them.
+1. Under the hood
+1.1. Metadata collection
+Reflection and annotation-based metadata is collected about persistent entities in org.neo4j.ogm.metadata.Metadata
+which provides it to any part of the library. This information is gathered by reading the class files directly rather
+than loading via reflection, resulting in much faster startup times.
The information is stored in ClassInfo instances which hold all the required object-graph mapping information for each
+type. This metadata is discovered at start-up by specifying a list of packages in which all classes are scanned,
+including those in sub-packages. In order to omit a class from being metadata-mapped you should annotate it with @Transient.
1.2. The Session object
+The Spring repositories and Neo4jTemplate are both backed by org.neo4j.ogm.session.Session, which is a key component
+of the framework. The Session provides methods to load, save or delete object graphs from the database and also
+provides transaction support. The new Neo4jTemplate is essentially a wrapper around this Session, which exposes
+all of its useful methods but handles transactions and provides the traditional Spring Data operations.
1.3. Explicit save
+Unlike the original AspectJ-driven mapping, Spring Data Neo4j 4 doesn’t automatically commit when a
+transaction closes, so an explicit call to save(…) is required in order to persist changes to the database.
1.4. Fine-grained control via depth specification
+Spring Data Neo4j introduces the concept of persistence horizon. On any individual request, the persistence horizon
+indicates how many edges should be traversed in the graph when loading or saving data. A horizon of zero means
+that only the root object’s properties will be loaded or saved, a horizon of 1 will include the root object and all
+its immediate neighbours, and so on. This attribute is enabled via a depth argument available on all repository and
+template methods, but SDN 4 chooses sensible defaults so that you don’t have to specify the depth attribute unless
+you want change the default values.
1.4.1. Default depth for loading
+By default, loading an instance will map that object’s simple properties and its immediately-related objects (i.e. depth = 1). +This helps to avoid accidentally loading the entire graph into memory, but allows a single request to fetch not only the +object of immediate interest, but also its closest neighbours, which are likely also to be of interest. This strategy +attempts to strike a balance between loading too much of the graph into memory and having to make repeated requests +for data.
+If parts of your graph structure a deep and not broad (for example a linked-list), you can increase the +load horizon for those nodes accordingly. Finally, if your graph will fit into memory, and you’d like to load +it all in one go, you can set the depth to -1.
+On the other hand when fetching structures which are potentially very "bushy" (e.g. lists of things that themselves have +many relationships), you may want to set the load horizon to 0 (depth = 0) to avoid loading thousands of objects most +of which you won’t actually inspect.
+1.4.2. Default depth for persisting
+When persisting changes to the model, the default depth is -1. This means that all affected objects in the entity model +that are reachable from the root object being persisted will be modified in the graph. This is the recommended approach +because it means you can persist all your changes in one request. The OGM is able to detect which objects and relationships +require changing, so you won’t flood Neo4j with a bunch of objects that don’t require modification. You can change +the persistence depth to any value, but you should not make it less than the value used to load the corresponding data or +you run the risk of not having changes you expect to be made actually being persisted in the graph.
+2. Simplified Object-Graph Mapping
+As of version 4, Spring Data Neo4j supports mapping annotated and non-annotated objects models. +It’s possible to save any POJO without annotations to the graph, as the framework applies conventions to decide what to do. +This is useful in cases when you don’t have control over the classes that you want to persist. +The recommended approach, however, is to use annotations wherever possible, since this gives greater control and means +that code can be refactored safely without risking breaking changes to the labels and relationships in your graph.
+Annotated and non-annoted objects can be used within the same project without issue. There is an EntityAccessStrategy
+used to control how objects are read from or written to. The default implementation of this uses the following convention:
-
+
-
+
Annotated method (getter/setter)
+
+ -
+
Annotated field
+
+ -
+
Plain method (getter/setter)
+
+ -
+
Plain field
+
+
The object graph mapping comes into play whenever an entity is constructed from a node or relationship. This could be done
+explicitly like during the lookup or create operations of the repositories and the Neo4jTemplate but also implicitly
+while executing any graph operation that returns nodes or relationships and expecting mapped entities to be returned.
Unless annotations are used to specify otherwise, the framework will attempt to map any of an object’s "simple" fields +to node properties and any rich composite objects to related nodes. A "simple" field is any primitive, boxed primitive +or String or arrays thereof, essentially anything that naturally fits into a Neo4j node property. For related entities +the type of a relationship is inferred by the bean property name, as outlined in the examples below.
+3. Defining node entities
+Node entities are declared using the @NodeEntity annotation. Relationship entities use the @RelationshipEntity annotation.
3.1. @NodeEntity: The basic building block
+The @NodeEntity annotation is used to declare that a POJO class is 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.
@NodeEntity annotations are inherited from super-types and interfaces. It is not necessary to annotate your domain objects at every inheritance level.
If the label attribute is set then this will replace the default label applied to the node in the database. The default label is just the simple class name of the annotated entity. All parent classes are also added as labels so that retrieving a collection of nodes via a parent type is supported.
Entity fields can be annotated with @Property, @GraphId, @Transient or @Relationship. Support for @Query on fields is likely to be available in the full release but is unsupported as of milestone 1. Marking a field with the transient modifier has the same effect as annotating it with @Transient; it won’t be persisted to the graph database.
@NodeEntity
+public class Actor extends DomainObject {
+
+ @GraphId
+ private Long id;
+
+ @Property(name="name")
+ private String fullName;
+
+ @Relationship(type="ACTED_IN", direction=Relationship.OUTGOING)
+ private List<Movie> filmography;
+
+}
+
+@NodeEntity(label="Film")
+public class Movie {
+
+ @Property(name="title")
+ private String name;
+
+}
+Saving a simple object graph containing one actor and one film using the above annotated objects would result in the following being persisted in Neo4j.
+(:Actor:DomainObject {name:'Tom Cruise'})-[:ACTED_IN]->(:Film {title:'Mission Impossible'})
+When annotating your objects, you can apply the annotations to either the fields or their accessor methods, but bear in mind the aforementioned EntityAccessStrategy ordering when annotating your domain model.
public class Actor extends DomainObject {
+
+ private Long id;
+ private String fullName;
+ private List<Movie> filmography;
+
+}
+
+public class Movie {
+
+ private String name;
+
+}
+In this case, a graph similar to the following would be persisted.
+(:Actor:DomainObject {fullName:'Tom Cruise'})-[:FILMOGRAPHY]->(:Movie {name:'Mission Impossible'})
+While this will map successfully to the database, it’s important to understand that the names of the properties and relationship types are tightly coupled to the class’s member names. Renaming any of these fields will cause parts of the graph to map incorrectly, hence the recommendation to use annotations.
+3.2. @GraphId: Neo4j id field
+This is a required field which must be of type Long. It is used by Spring Data Neo4j to store the node or relationship-id to re-connect the entity to the graph.
|
+ Note
+ |
+
+It must not be a primitive type because then an object in a transient state cannot be represented, as the default value 0 would point to the reference node. Please also make sure that an equals() and hashCode() method have to be provided which take the id field into account (and also handle the "non-attached", null case).
+ |
+
If the field is simply named 'id' then it is not necessary to annotate it with @GraphId as the OGM will use it automatically.
3.2.1. Entity Equality
+Entity equality can be a grey area, and it is debatable whether natural keys or database ids best describe equality, there is the issue of versioning over time, etc. In previous versions of Spring Data Neo4j it was recommended to honour the convention that database-issued ids are the basis for equality, despite the consequences.
+In version 4, the dependency of the framework upon a particular style of equals() or hashCode() implementation has been abolished. The graph ID field is directly checked to see if two entities represent the same node and a 64-bit hash code is used for dirty checking, so you’re not forced to write your code in a certain way - a sin committed by many other mapping frameworks out there!
However, we do think it’s important to mention that if you use the @GraphId field in your hashCode() method then this comes with a caveat. When you first persist an entity, its hashcode changes because Spring Data Neo4j populates the database ID on save.
That causes problems if you had inserted the newly created entity into a hash-based collection before saving. While that can be worked around, we strongly advise you adopt a convention of not relying upon the graph ID for object equality.
+3.3. @Property: Optional annotation for property fields
+As we touched on earlier, it is not necessary to annotate property fields as they are persisted by default. All fields that contain primitive values are persisted directly to the graph. All fields convertible to a String using the Spring conversion services will be stored as a string. Spring Data Neo4j includes default type converters that deal with the following types:
-
+
-
+
+java.util.Dateto a String in the ISO 8601 format: "yyyy-MM-dd’T’HH:mm:ss.SSSXXX"
+ -
+
+java.math.BigIntegerto a String property
+ -
+
+java.math.BigDecimalto a String property
+ -
+
binary data (as byte[] or Byte[]) to base-64 String
+
+ -
+
+java.lang.Enumtypes using the enum’sname()method andEnum.valueOf()
+
Collections of primitive or convertible values are stored as well. They are converted to arrays of their type or strings respectively. Custom converters are also specified by using @Convert - this is discussed in detail later on.
Node property names can be explicitly assigned by setting the name attribute. For example @Property(name="last_name") String lastName. The node property name defaults to the field name when not specified.
3.4. @Query: fields as query result views
+|
+ Note
+ |
++This is not supported on entity fields for milestone one, although it does work in repositories + | +
The @Query annotation leverages the delegation infrastructure supported by Spring Data Neo4j. 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 the current entity.
+For instance the query MATCH ({self}-[:FRIEND_OF]→(friend) RETURN friend. Graph queries can return variable number of entities. That’s why annotation can be put onto fields with a single value, a subclass of Iterable of a concrete type or an Iterable of Map<String,Object>. Additional parameters are taken from the params attribute of the @Query annotation. These parameter tuples form key-value pairs that are provided to the query at execution time.
@NodeEntity
+public class Group {
+ @Query(value = "MATCH ({self})-[r]->(friend) WHERE r.type={relType} RETURN friend",
+ params = {"relType", "FRIEND_OF"})
+ private Iterable<Person> friends;
+}
+4. 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.
+4.1. @Relationship: 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 when the entity is persisted. If the field is set to null, the relationship is removed.
@NodeEntity
+public class Movie {
+ private Actor topActor;
+}
+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 Collection<T>, and read-only fields are Iterable<T>, where T is a type annotated with @NodeEntity.
@NodeEntity
+public class Actor {
+ @Relationship(type = "TOP_ACTOR", direction = Relationship.INCOMING)
+ private Set<Movie> topActorIn;
+
+ @Relationship(type = "ACTS_IN")
+ private Set<Movie> movies;
+}
+For graph to object mapping, the automatic transitive loading of related entities depends on the depth of the search specified on the call to Session.load(). By default, the related node or relationship entities will just be loaded to minimum depth 0, which means their properties will be set but no further related entities will be populated.
If this Set of related entities is modified, the changes are reflected in the graph once the root object (Actor, in this case) is saved. Relationships are added, removed or updated according to the differences between the root object that was loaded and the corresponding one that was saved..
Spring Data Neo4j ensures by default that there is only one relationship of a given type between any two given entities. In previous versions of the framework Direction.BOTH could be used but this is no longer supported, since relationships are navigable in both directions anyway so duplicates don’t need to be created. If you don’t care about the direction then you can specify direction=Relationship.UNDIRECTED which will guarantee that the path between two node entities is navigable from either side..
4.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 @Relationship provide a way to only link node entities via relationships, but it provides no way of accessing the relationships themselves.
Fields in relationship entities are similar to node entities, in that they’re 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.
For the relationship-type a String attribute called type is available on the @RelationshipEntity annotation. Rather like the simple strategy for labelling node entities, if this is unset then the name of the class is used to derive the relationship type.
|
+ Note
+ |
+
+You must include @RelationshipEntity plus exactly one @StartNode field and one @EndNode field on your relationship entity classes or the OGM will throw a MappingException when reading or writing. It is not possible to use relationship entities in a non-annotated domain model.
+ |
+
@NodeEntity
+public class Actor {
+
+ private Role playedIn;
+
+}
+
+@RelationshipEntity
+public class Role {
+
+ @GraphId
+ private Long relationshipId;
+ @Property(name="partPlayed")
+ private String title;
+ @StartNode
+ private Actor actor;
+ @EndNode
+ private Movie movie;
+
+}
+
+@NodeEntity
+public class Movie {
+
+ private String title;
+
+}
+In previous versions of Spring Data Neo4j, a dynamic relationship type was supported. However, this has been dropped completely for version 4, since it was not possible to manage it effectively for both reading from and writing to the graph.
+4.3. Discriminating Relationships Based on End Node Type
+In some cases, you want to model two different aspects of a conceptual relationship using the same relationship type. +Here is a canonical example:
+@NodeEntity
+class Person {
+ @Relationship(type="OWNS")
+ private Car car;
+
+ @Relationship(type="OWNS")
+ private Pet pet;
+...
+}
+In previous versions of Spring Data Neo4j, you would have to add an enforceTargetType attribute into every clashing
+@Relationship annotation for this to map correctly. Thanks to changes in the underlying object-graph mapping mechanism,
+this is no longer necessary and the above will work just fine.
However, please be aware that this will only work because the end node types (Car and Pet) are different types.
+If you wanted a person to own two cars, for example, then you’d have to use a Collection of cars or use differently-named relationship types.
5. Indexing
+Indexing is used in Neo4j to quickly find nodes and relationships from which to start graph operations. Either for manually traversing the graph, using the traversal framework, cypher queries or for "global" graph operations. Indexes are also employed to ensure uniqueness of elements with certain labels and properties.
+|
+ Note
+ |
++Please note that the lucene-based manual indexes are deprecated with Neo4j 2.0. The default index is now based on labels and schema indexes and the related old APIs have been deprecated as well. The "legacy" index framework should only be used for fulltext and spatial indexes which are not currently supported via schema-based indexes. + | +
5.1. Index Management in Spring Data Neo4j 4
+From Spring Data Neo4j 4, @Indexed no longer exists. The reason for this is because index creation should not be the job of the mapping framework, but should instead be managed within the database environment and tuned for the particular use case of your application. Indexes shouldn’t be added arbitrarily to fields by developers, but should instead be the result of design decisions made alongside the rest of the performance requirements.
Index creation and management is therefore now outside the scope of this document. Please see the Neo4j documention on indexes for for information: http://neo4j.com/docs/stable/query-schema-index.html
+5.2. Index queries in Neo4jTemplate
+For querying on indexes, certainly as of SDN 4 milestone one, your best bet is to take advantage of the GraphRepository load-by-property methods, or the corresponding ones on Neo4jTemplate. If you know that particular properties are indexed in the database then this will ensure that the indexes are utilised so you still take advantage of the performance benefits.
In the full release, the ability to do this through Spring repositories should also be fully implemented.
+5.3. Neo4j Auto Indexes
+Neo4j allows to configure (legacy) auto-indexing for certain properties on nodes and relationships. It is possible to use the specific index names node_auto_index and relationship_auto_index when querying indexes in Spring Data Neo4j either with the query methods in template and repositories or via Cypher.
5.4. Spatial Indexes
+Older versions of Spring Data Neo4j offered limited support for spatial queries using the neo4j-spatial library. However, as of SDN 4 milestone 1 at least, this is no longer supported.
6. Neo4jTemplate
+The Neo4jTemplate offers the convenient API of Spring templates for the Neo4j graph database. As of version 4, the Spring Data Neo4j Template wraps the underlying object-graph mapping Session, but still provides the core functionality to persist objects to the graph and load them in a variety of ways. Indeed, you can just use the Session directly in your code if you need greater control, but the Neo4jTemplate may well be easier for general use.
6.1. Basic operations
+For Spring Data Neo4j 4, the changes to the underlying architecture have led to the pruning of the Neo4jTemplate feature set. Basic operations are now entirely limited to CRUD operations on entities and executing arbitrary Cypher queries; more low-level manipulation of the graph database is not possible.
|
+ Note
+ |
++There is no longer a way to manipulate relationships and nodes directly. + | +
Given that the latest version of the framework is driven by Cypher queries alone, there’s no way to work directly with Node and Relationship objects any more in remote server mode. Similarly, the traverse() method has disappeared, again because the underlying query-driven model doesn’t handle it in an efficient way.
If you find yourself in trouble because of the omission of these features, then your best options are:
+-
+
-
+
Write a Cypher query to perform the operations on the nodes/relationships instead
+
+ -
+
Write a Neo4j server plugin and call it over REST from your application
+
+
Of course, there are pros and cons to both of these approaches, but these are largely outside the scope of this document. In general, for low-level operations like complex graph traversals you’ll get the best performance by writing a server-side plugin, which is generally easier to test and maintain than the sort of solution available with earlier versions of SDN anyway. For many purposes, though, Cypher will be performant and expressive enough to perform the operations that you need.
+6.2. Entity-Persistence
+Neo4jTemplate allows to save, load(One/All) and delete entities. However, as of SDN 4, it no longer provides the stored type information via getStoredJavaType. The eagerness with which objects are retrieved is controlled by specifying the 'depth' argument to any of the load methods.
All of these basic CRUD methods just call onto the corresponding methods of Session, albeit with transaction handling thrown in to save you the effort of manually managing them.
6.3. Cypher Queries
+The Neo4jTemplate also allows execution of arbitrary Cypher queries via its query, queryForObject and queryForObjects methods. Cypher queries that return tabular results should be passed into the query method and an Iterable<Map<String,Object>> containing the raw data is returned, which can be either used as it is or converted as needed.
For the query methods that retrieve mapped objects, the recommended query format is to return a path, which should ensure that known types get mapped correctly and joined together with relationships as appropriate.
+6.4. Transactions
+The Neo4jTemplate provides implicit transactions for some of its methods. For instance save uses them. For other modifying operations please provide Spring Transaction management using @Transactional or the TransactionTemplate.
6.5. Lifecycle Events
+|
+ Note
+ |
++These events are not implemented as of milestone 1 of SDN 4. + | +
Neo4j Template offers basic lifecycle events via Spring’s event mechanism using ApplicationListener and ApplicationEvent. The following hooks are available in the form of types of application event:
+-
+
-
+
BeforeSaveEvent
+
+ -
+
AfterSaveEvent
+
+ -
+
DeleteEvent - after the event has been deleted
+
+
The following example demonstrates how to hook into the application lifecycle and register listeners that perform behaviour across types of entities during this life cycle:
+@Configuration
+@EnableNeo4jRepositories
+public class ApplicationConfig extends Neo4jConfiguration {
+ ...
+ @Bean
+ ApplicationListener<BeforeSaveEvent> beforeSaveEventApplicationListener() {
+ return new ApplicationListener<BeforeSaveEvent>() {
+ @Override
+ public void onApplicationEvent(BeforeSaveEvent event) {
+ AcmeEntity entity = (AcmeEntity) event.getEntity();
+ entity.setUniqueId(acmeIdFactory.create());
+ }
+ };
+ }
+
+ @Bean
+ ApplicationListener<AfterSaveEvent> afterSaveEventApplicationListener() {
+ return new ApplicationListener<AfterSaveEvent>() {
+ @Override
+ public void onApplicationEvent(AfterSaveEvent event) {
+ AcmeEntity entity = (AcmeEntity) event.getEntity();
+ auditLog.onEventSaved(entity);
+ }
+ };
+ }
+
+ @Bean
+ ApplicationListener<DeleteEvent> deleteEventApplicationListener() {
+ return new ApplicationListener<DeleteEvent>() {
+ @Override
+ public void onApplicationEvent(DeleteEvent event) {
+ AcmeEntity entity = (AcmeEntity) event.getEntity();
+ auditLog.onEventDeleted(entity);
+ }
+ };
+ }
+ ...
+Changes made to entities in the before-save event handler are reflected in the stored entity - after-save ones are not.
+7. 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 comes with a single org.springframework.data.repository.CrudRepository specialisation called
+GraphRepository<T> used for all object-graph mapping repositories. This sub-interface also adds specific finder methods
+that take a depth argument to control the eagerness with which related entities are fetched. Generally, it has all the
+desired repository methods. If other operations are required then the additional repository interfaces should be added
+to the individual interface declaration.
|
+ Note
+ |
+
+GraphRepository no longer combines IndexRepository and TraversalRepository because, for reasons explained
+above, these features are no longer supported in Spring Data Neo4j as of version 4.
+ |
+
7.1. GraphRepository
+As of SDN 4, this GraphRepository<T> should be the interface from which your entity repository interfaces inherit,
+with T being specified as the node entity type to persist.
There is also no TypeRepresentationStrategy in the latest framework version. Instead, the type representation strategy
+has been greatly simplified to use a single label-based mechanism as described in more detail here: [reference_programming-model_typerepresentationstrategy]
Examples of methods you get for free out of GraphRepository are as follows. For all of these examples the ID parameter
+is a Long that matches the graph ID:
-
+
- Load an entity instance via an id +
-
+
+T findOne(id)
+ - Check for existence of an id in the graph +
-
+
+boolean exists(id)
+ - Iterate over all nodes of a node entity type +
-
+
+Iterable<T> findAll()Iterable<T> findAll(Sort)
+ - Count the instances of the repository entity type +
-
+
+Long count()
+ - Save entities +
-
+
+T save(T)andIterable<T> save(Iterable<T>)
+ - Delete graph entities +
-
+
+void delete(T),void delete(Iterable<T>), andvoid deleteAll()
+
7.2. Query and Finder Methods
+7.2.1. Annotated queries
+Queries using the Cypher graph query language can be supplied with the @Query annotation. That means every method
+annotated with @Query("start n=node:IndexName(key={node or 0}) match (n)-→(m) return m") will use the supplied query string. The named or indexed parameter {node} will be substituted by the actual method parameter. Node and Relationship-Entities are handled directly, Iterables thereof as well. All other parameters are replaced directly (i.e. Strings, Longs, etc).
|
+ Note
+ |
++As of milestone 1 of Spring Data Neo4j 4, paging is not natively supported + | +
7.2.2. Query results
+Typical results for queries are Iterable<Type>, Iterable<Map<String,Object>> or Type. As of milestone one,
+Slice<Type> and Page<Type> are not supported. Nodes and relationships are converted to their respective entities
+(if they exist). Other values are converted using the registered conversion
+services (e.g. enums).
7.2.3. Cypher examples
+-
+
MATCH (n) WHERE id(n)=9 RETURN n
+-
+
returns the node with id 9
+
+ MATCH (movie:Movie {title:'Matrix'}) RETURN movie
+-
+
returns the nodes which are indexed with title equal to 'Matrix'
+
+ MATCH (movie:Movie {title:'Matrix'})←[:ACTS_IN]-(actor) RETURN actor.name
+-
+
returns the names of the actors that have a ACTS_IN relationship to the movie node for 'Matrix'
+
+ MATCH (movie:Movie {title:'Matrix'})←[r:RATED]-(user) WHERE r.stars > 3 RETURN user.name, r.stars, r.comment
+-
+
returns users names and their ratings (>3) of the movie titled 'Matrix'
+
+ (user:User {login='micha'})-[:FRIEND]-(friend)-[r:RATED]→(movie) RETURN movie.title, AVG(r.stars), COUNT() ORDER BY AVG(r.stars) DESC, COUNT() DESC
+-
+
returns the movies rated by the friends of the user 'micha', aggregated by movie.title, with averaged ratings and rating-counts sorted by both
+
+
as described in the Annotated queries) section.
+public interface MovieRepository extends GraphRepository<Movie> {
+
+ // returns the node with id equal to idOfMovie parameter
+ @Query("MATCH (n) WHERE id(n)={0} RETURN n")
+ Movie getMovieFromId(Integer idOfMovie);
+
+ // returns the nodes which will use index named title equal to movieTitle parameter
+ // movieTitle String must not contain any spaces, otherwise you will receive a NullPointerException.
+ @Query("MATCH (movie:Movie {title={0}}) RETURN movie")
+ Movie getMovieFromTitle(String movieTitle);
+
+ // returns the Actors that have a ACTS_IN relationship to the movie node with the title equal to movieTitle parameter.
+ // (The parenthesis around 'movie' and 'actor' in the match clause are optional.)
+ @Query("MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN actor")
+ Page<Actor> getActorsThatActInMovieFromTitle(String movieTitle, PageRequest);
+
+ // returns users who rated a movie (movie parameter) higher than rating (rating parameter)
+ @Query("MATCH (movie:Movie)<-[r:RATED]-(user) " +
+ "WHERE id(movie)={0} AND r.stars > {1} " +
+ "RETURN user")
+ Iterable<User> getUsersWhoRatedMovieFromTitle(Movie movie, Integer rating);
+
+ // returns users who rated a movie based on movie title (movieTitle parameter) higher than rating (rating parameter)
+ @Query("MATCH (movie:Movie {title:{0}})<-[r:RATED]-(user) " +
+ "WHERE r.stars > {1} " +
+ "RETURN user")
+ Iterable<User> getUsersWhoRatedMovieFromTitle(String movieTitle, Integer rating);
+ }
+7.2.4. Queries derived from finder-method names
+As known from Rails or Grails it is possible to derive queries for domain entities from finder method names like
+Iterable<Person> findByNameAndAgeGreaterThan(String name, int age). Using the metadata infrastructure in the underlying
+object-graph mapper, a finder method name can be split into its semantic parts and converted into a cypher query. Navigation along relationships will be reflected in the generated MATCH clause and properties with operators will end up as expressions in the WHERE clause. Order and limiting of the query will by handled by provided Pageable or Sort parameters. The other parameters will be used in the order they appear in the method signature so they should align with the expressions stated in the method name.
|
+ Note
+ |
++This feature is unsupported as of SDN 4 Milestone 1 + | +
public interface PersonRepository extends GraphRepository<Person> {
+
+ // MATCH (person:Person {name={0}}) RETURN person
+ Person findByName(String name);
+
+ // MATCH (person:Person) WHERE person.name =~ '.*{0}.*' RETURN person
+ Iterable<Person> findByNameLike(String name)
+
+ // MATCH (person:Person)
+ // WHERE person.age = {0} AND person.married = {1}
+ // RETURN person
+ Iterable<Person> findByAgeAndMarried(int age, boolean married)
+
+}
+7.2.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.
+|
+ Note
+ |
++This feature is unsupported as of SDN 4 Milestone 1 + | +
@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
+ @Relationship(type = "group", direction = Relationship.INCOMING)
+ private Set<Person> members = new HashSet<>();
+
+ 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));
+7.3. Creating repositories
+The Repository instances are created through Spring and can be auto-wired into your Spring beans as required.
+In previous versions, it was possible to create repositories using Neo4jTemplate but this is no longer possible
+as of SDN 4..
@Repository
+public interface PersonRepository extends GraphRepository<Person> {}
+
+public class MySpringBean {
+ @Autowired
+ private PersonRepository repo;
+ ...
+}
+
+// then you can use the repository as you would any other object
+Person michael = repo.save(new Person("Michael", 36));
+
+Person dave = repo.findOne(123);
+
+Long numberOfPeople = repo.count();
+
+EndResult<Person> devs = graphRepository.findAllByProperty("occupation", "developer");
+The recommended way of providing repositories is to define a repository interface per domain class. The undelying Spring +repository infrastructure will automatically detect these repositories, along with additional implementation classes, +and create an injectable repository implementation to be used in services or other spring beans.
+@Configuration
+@ComponentScan({"com.example.sdn"})
+@EnableNeo4jRepositories("com.example.sdn.repo")
+@EnableTransactionManagement
+public class PersistenceContext {
+
+ @Bean
+ public SessionFactory getSessionFactory() {
+ return new SessionFactory("com.example.sdn.domain");
+ }
+ // more bean definition methods here
+}
+8. Conversion
+The object-graph mapping framework on which Spring Data Neo4j is built provides support for default and bespoke type +conversions, which allow you to configure how certain data types are mapped to nodes or relationships in Neo4j.
+8.1. Built-In Type Conversions
+By default, Spring Data Neo4j will automatically perform the following type conversions:
+-
+
-
+
+java.util.Dateto a String in the ISO 8601 format: "yyyy-MM-dd’T’HH:mm:ss.SSSXXX"
+ -
+
+java.math.BigIntegerto a String property
+ -
+
+java.math.BigDecimalto a String property
+ -
+
binary data (as byte[] or Byte[]) to base-64 String
+
+ -
+
+java.lang.Enumtypes using the enum’sname()method andEnum.valueOf()
+
Two Date converters are provided "out of the box"
+-
+
-
+
@DateString
+
+ -
+
@DateLong
+
+
By default, SDN will use the @DateString converter as described above. However if you want to use a different date +format, you can annotate your entity attribute accordingly:
+---
+public class MyEntity {
+ @DateString("yy-MM-dd")
+ private Date entityDate;
+}
+---
+===
+Alternatively, if you want to store Dates as long values, use the @DateLong annotation:
+---
+public class MyEntity {
+@DateLong + private Date entityDate; +} +--- +===+
Collections of primitive or convertible values are also automatically mapped by converting them to arrays of their type +or strings respectively.
+8.2. Custom Type Conversion
+In order to define bespoke type conversions for particular members, you can annotate a field or method with @Convert
+to specify an implementation of org.neo4j.ogm.typeconversion.AttributeConverter to use.
public class MoneyConverter implements AttributeConverter<DecimalCurrencyAmount, Integer> {
+
+ @Override
+ public Integer toGraphProperty(DecimalCurrencyAmount value) {
+ return value.getFullUnits() * 100 + value.getSubUnits();
+ }
+
+ @Override
+ public DecimalCurrencyAmount toEntityAttribute(Integer value) {
+ return new DecimalCurrencyAmount(value / 100, value % 100);
+ }
+
+}
+You could then apply this to your class as follows:
+@NodeEntity
+public class Invoice {
+
+ @Convert(MoneyConverter.class)
+ private DecimalCurrencyAmount value;
+ ...
+}
+8.3. Mapping Query Results
+|
+ Note
+ |
++This feature isn’t supported as of SDN 4 Milestone 1 + | +
For queries executed via @Query repository methods, it’s possible to specify a conversion of complex query results to POJOs. These result objects are then populated with the query result data and can be serialized and sent to a different part of the applicaton, e.g. a frontend-ui.
Use a class annotated with @QueryResult as the method return type and SDN will apply the same simple mapping strategy as it does for normal entities.
public interface MovieRepository extends GraphRepository<Movie> {
+
+ @Query("MATCH (movie:Movie)-[r:RATING]->(), (movie)<-[:ACTS_IN]-(actor:Actor) " +
+ "WHERE movie.id={0} " +
+ "RETURN movie, COLLECT(actor) AS 'cast', AVG(r.stars) AS 'averageRating'")
+ MovieData getMovieData(String movieId);
+
+ @QueryResult
+ public class MovieData {
+ Movie movie;
+ Double averageRating;
+ Collection<Actor> cast;
+ }
+
+}
+9. 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 nicely with both the declarative transaction support with @Transactional as well as the manual transaction handling with TransactionTemplate. It also supports the rollback mechanisms of the Spring Testing library.
As of version 4.0, the classes used to perform transaction management have been rewritten. Instead of using SpringTransactionManager provided by the Neo4j kernel alongside Spring’s JtaTransactionManager, the transaction management is performed by Neo4jTransactionManager, which implements Spring’s PlatformTransactionManager.
+This Neo4jTransactionManager is based on an OGM Session, on which the beginTransaction() method gets called, and this in turn delegates onto the underlying OGM’s TransactionManager implementation.
The Neo4jConfiguration Spring configuration bean will create an instance of this Neo4jTransactionManager for use in Spring Data Neo4j. It is made available under the name "transactionManager" in the Spring application context.
|
+ Note
+ |
++Neither XML-based configuration nor multi-resource transactions are supported in SDN 4 M1 + | +
For scenarios with multiple transactional resources there are two options. The first option is to have Neo4j participate in the externally-configured transaction manager using the Spring support in Neo4j by enabling the configuration parameter for your graph database. Neo4j will then use Spring’s transaction manager instead of its own.
+<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 id="graphDatabaseService" 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 also configure a custom 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.
|
+ Note
+ |
++As of Neo4j 2.2, there is no XA transaction participation support + | +
<bean id="jpaTransactionManager"
+ class="org.springframework.orm.jpa.JpaTransactionManager">
+ <property name="entityManagerFactory" ref="entityManagerFactory"/>
+</bean>
+<bean id="jtaTransactionManager"
+ class="org.springframework.data.neo4j.config.JtaTransactionManagerFactoryBean">
+ <constructor-arg ref="graphDatabaseService"/>
+</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"/>
+10. Entity Attachment
+In previous versions of Spring Data Neo4j, entities could be "attached" or "detached" depending on whether or not they were enhanced by AspectJ and actively managed by the framework. As of SDN 4, this is no longer the case and the AspectJ involvement has completely gone away.
+The motivation for removing this was to achieve greater simplicity. AspectJ-enhanced methods can provide a way to right neat, object-oriented code, but often it can be confusing to know whether an entity is managed or not, and the way in which cascading worked from "magic" methods like entity.persist() often caused confusion. The new method is much simpler.
10.1. Persisting Entities
+From version 4 onwards, the entity persistence is all performed through the save() method on the Session object. This method is normally invoked indirectly via a Spring repository or Neo4jTemplate, but will work just as well if invoked directly if you’re working with the Session in its raw form.
Under the bonnet, the implementation of Session has access to the MappingContext that keeps track of the data that has been loaded from Neo4j during the lifetime of the session. Upon invocation of save() with an entity, it checks the given object graph for changes compared with the data that was loaded from the database. The differences are used to construct a Cypher query that persists the deltas to Neo4j before repopulating it’s state based on the response from the database server.
One thing that hasn’t changed in version 4 is the fact that all entities returned by library functions are initially in an attached state, and those created with the new keyword aren’t. However, there’s no longer any attachment or detachment that goes on depending on where certain code is executed, because entities aren’t managed with AspectJ enhancements. Therefore calling save() is always required in order to persist your changes back to Neo4j.
@NodeEntity
+public class Person {
+ private String name;
+ public Person(String name) {
+ this.name = name;
+ }
+}
+
+// Store Michael in the database.
+Person p = new Person("Michael");
+personRepository.save(p);
+// or alternatively
+neo4jTemplate.save(p);
+10.2. Save Depth
+As mentioned previously, save(entity) is overloaded as save(entity, depth), where depth dictates the number of related entities to save starting from the given entity. A depth of 0 will persist only the properties of the specified entity to the database, and a depth of -1 will persist everything in the object graph rooted at the given entity.
Specifying the save depth is handy when it comes to dealing with complex collections, that could potentially be very expensive to load.
+|
+ Note
+ |
++If you’re using this overloaded method rather than the repositories, it’s strongly recommended to use depth consistently between load and save invocations. If you don’t then you may unexpectedly see relationships deleted or updates not persisting as you expect. + | +
@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 repository.save(movie), 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 save the actor 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.
The important thing to note here is that if repository.save(actor) 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 behaviour 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.
If the relationships form a cycle, then the entities will first of all be assigned a node in the database, and then the relationships will be created. The cascading is however only propagated 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:
+actor.setName("Billy Bob");
+movieRepository.save(movie);
+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.
11. Entity Type Representation
+As of Spring Data Neo4j 4, type representation has been greatly simplified to the point that there is just one strategy.
+The TypeRepresentationStrategy has disappeared and a single label-based model is all that is supported.
For @NodeEntity classes, the simple names of the class and each of its parent classes (excluding java.lang.Object)
+is written as a node label. This node label is used in Cypher queries generated by the OGM to find objects of a particular type,
+and by labelling using superclasses as well it becomes possible to retrieve collections of entities as abstract super types.
@NodeEntity
+public abstract class DomainObject {
+ @GraphId
+ protected Long id;
+}
+
+public class Person extends DomainObject {
+ ...
+}
+
+public class Lady extends Person {
+ ...
+}
+
+public class Gentleman extends Person {
+ ...
+}
+
+// creates a node with labels Gentleman:Person:DomainObject
+repository.save(new Gentleman());
+
+// retrieve all ladies and gentlemen
+Collection<Person> people = repository.loadAll(Person.class);
+The label applied to a node in the database can be configured by setting the value of the label property in the
+@NodeEntity annotation.
For @RelationshipEntity classes, the simple name of the class is used to derive the relationship type to use,
+although it’s converted into SNAKE_CASE to honour the typical naming conventions of Neo4j relationships. Much like node
+entities, you can configure the relationship type to use by setting the type property in the @RelationshipEntity
+annotation.
11.1. Type Aliases
+|
+ Note
+ |
+
+As of SDN 4 milestone 1, @TypeAlias is not currently supported
+ |
+
As some type information is also stored in labels, node/relationship-properties and/or indexes it might amount to a
+substantial amount of data in the graph. It is possible to use an @TypeAlias("name") annotation on nodes and
+relationships to have a short constant name for each type which is (unlike the default approach) renaming-refactoring-safe.
12. Bean Validation (JSR-303)
+|
+ Note
+ |
++As of Spring Data Neo4j 4 milestone 1, Java standards validation is not supported + | +
Spring Data Neo4j supports property-based validation as defined by JSR-303. When a property is changed and persisted, it is checked against the annotated constraints, such as @Min, @Max and @Size. Validation errors cause a ValidationException to be thrown. 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 Neo4jTemplate, which is done automatically by the Neo4jConfiguration if one is present in the Spring Config.
@NodeEntity
+class Person {
+ @Size(min = 3, max = 20)
+ String name;
+
+ @Min(0)
+ @Max(100)
+ int age;
+}
+The validation supports needs the bean validation API and a reference implementation configured. Right now this is the Hibernate Validator by default (which is not integrated with Hibernate ORM). The maven dependency is:
+<dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ <version>4.2.0.Final</version>
+</dependency>
+Performance considerations
+Like with any other object mapping framework, the domain entities that are created, read, or persisted potentially +represent only a small fraction of the data stored in the database. This is the set needed for a certain use-case to +be displayed, edited or processed in a low throughput fashion. The main advantages of using an object mapper in this +case are the ease of use of real domain objects in your business logic and also the integration with existing frameworks +and libraries that expect Java POJOs as input or create them as results.
+Although adding layers of abstraction is a common pattern in software development, each of these layers generally adds +overhead and performance penalties. This chapter discusses the performance implications of using Spring Data Neo4j.
+A little history
+Previous versions of Neo4j were not designed with a major focus on performance. being very much geared to applications +using an embedded instance of Neo4j, or where the Neo4j server was available via a managed extension.
+Consequently they were not really suited to running with remote instances, and in fact, until the advent +of Cypher over HTTP, this wasn’t even technically possible. Furthermore, attempts to do so very often resulted in +very "chatty" applications where multiple requests needed to be made to fully hydrate a single object. These +observations should not be seen as criticisms, because it was all that was possible at the time.
+Since then however Neo4j has moved on, and now, so has Spring Data Neo4j
+Focus on performance
+The new version of SDN has been rebuilt from the ground up, on the understanding that the majority of users want to +run application servers that connect to remote database instances, and that they will therefore need to communicate +"over the wire". Neo4j provides the capability to do this now with its powerful Cypher language, which is exposed +over HTTP directly from the server.
+What we have attempted to do is to ensure that, as much as possible, we don’t overload that communication. This is +important for two reasons. Firstly, every network interaction involves an overhead, and the more data it contains, the +greater the impact on the response times of the application. Secondly, network requests containing redundant operations +(such as updaing an object which hasn’t changed) simply waste CPU cycles on the server, and again impact on the overall +response times of the application. We have approached this problem in a number of ways:
+Variable-depth persistence
+You can now tailor your persistence requests according to the characteristics of the portions of your graph +you want to work with. This means you can choose to make deeper or shallower fetches based on fine tuning +the types and amounts of data you want versus your available bandwidth and memory constraints.
+If you know that you aren’t going to need an object’s related objects, you can choose not to fetch them by setting +the depth to 0. Alternatively if you know that you will always want to a person’s complete set of friends-of-friends, +you can set the depth to 2.
+Smart object-mapping
+SDN 4 introduces smart object-mapping. This means that all other things being equal, it is possible to reliably detect +which nodes and relationships needs to be changed in the database, and which don’t. Knowing what needs to be changed means +we don’t need to flood Neo4j with requests to update objects that don’t require changing, or create relationships +that already exist. We can minimise the amount of data we send across the wire as a result, which results in a faster +network interaction, and fewer CPU cycles consumed on the server.
+User-definable Session lifetime
+Supporting the smart object-mapping capability is the Neo4jSession. This object can be declared with +different lifetimes, depending on the requirements of your application. For web-based applications, you might choose +between HTTP Request-scoped lifetime or HTTP Session-scoped lifetimes. For a standalone application, you may choose +to maintain a single session for the entire lifetime of the application.
+The advantage of longer-running sessions is that you will be able to make more efficient requests to the database at the +expense of the additional memory associated with the session. The advantage of shorter sessions is they +impose almost no overhead on memory, but will result in less efficient requests to Neo4j when saving data.
+It is also possible to manage your session lifetimes in code. For example, associated with single fetch-update-save cycle
+or unit of work.
Migrating from previous versions of Spring Data Neo4j
+1.1. Package changes
+Because the Neo4j Object Graph Mapper can be used independently of the Spring wrappers, the core annotations have been +moved out of the spring framework packages:
+-
+
-
+
+org.springframework.data.neo4j.annotation→org.neo4j.ogm.annotation
+
The exception to this is the @Query which is not supported as an annotation in the core OGM.
1.2. Annotation changes
+There have been some changes to the annotations that were used in previous versions of Spring Data Neo4j. +Wherever possible we have tried to maintain the previous annotations verbatim, but in a few cases this has not been +possible, usually for technical reasons but sometimes for aesthetic ones. Our goal has been to minimise the number +of annotations you need to use as well as trying to make them more self-explanatory. The following annotations +have been changed.
+-
+
-
+
+@RelatedTo→@Relationship
+ -
+
+@RelatedToVia→@Relationship
+ -
+
+@GraphProperty→@Property
+ -
+
Relationship
+Direction.BOTH→Relationship.UNDIRECTED
+
1.3. Custom Type Conversion
+SDN 4 provides automatic type conversion for the obvious candidates: byte[] and Byte[] arrays, Dates, BigDecimal and
+BigInteger types. In order to define bespoke type conversions for particular entity attribute, you can annotate a
+field or method with @Convert to specify your own implementation of org.neo4j.ogm.typeconversion.AttributeConverter.
You can find out more about type conversions here: Custom Type Conversion
+1.4. Obsolete annotations
+The following annotations are no longer used, either because they are no longer needed, cannot be supported via Cypher, or +simply because they are a bad idea.
+-
+
-
+
@GraphTraversal
+
+ -
+
@RelatedToVia
+
+ -
+
@Index
+
+ -
+
@TypeAlias
+
+
1.5. Features no longer supported
+Some features of the previous annotations have been dropped.
+1.5.1. Overriding @Property types
+Support for overriding property types via arguments to @Property has been dropped. If your attribute requires +a non-default conversion to and from a database property, you can use a Custom Converter instead.
+1.5.2. @Relationship enforceTargetType
+In previous versions of Spring Data Neo4j, you would have to add an enforceTargetType attribute into every clashing
+@Relationship annotation. Thanks to changes in the underlying object-graph mapping mechanism, this is no longer
+necessary and this now works just fine.
@NodeEntity
+class Person {
+ @Relationship(type="OWNS")
+ private Car car;
+
+ @Relationship(type="OWNS")
+ private Pet pet;
+...
+}
+1.5.3. Cross-store persistence
+Neo4j is dropping XA support and therefore SDN 4 does not provide any capability for cross-store persistence
+1.5.4. Query DSL with Neo4jTemplate
+Previous versions of SDN allowed you to use a DSL to generate Cypher queries. There are many different DSL +libraries available and you’re free to use which of these - or none - that you want. With Cypher changing on a regular +basis, avoiding a DSL implementation in SDN means less ongoing maintenance and less likelihood of your code +being incompatible with future versions of Neo4j.
+1.5.5. Graph traversal and node/relationship manipulation with Neo4jTemplate
+These features cannot be supported by Cypher and have therefore been dropped from Neo4jTemplate.
+1.5.6. TypeRepresentationStrategy
+SDN 4 replaces the existing TypeRepresentionStrategy configuration with a straightforwad convention based on simple class-names
+or entities using @NodeEntity(label=…)
Please refer to Entity Type Representation for more details.
+1.5.7. AspectJ support dropped
+Due to numerous problems reported by users in earlier versions of SDN, support for Aspect J based persistence has +been removed from SDN 4.
+1.6. Features not yet included in Milestone 1 but that are on the Roadmap
+-
+
-
+
Geospatial queries
+
+ -
+
@QueryResult
+
+