diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..af000ad8d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +*/target/ +*.iml +spring-data-neo4j-examples/sdn-boot/bower_components/ diff --git a/pom.xml b/pom.xml index 9e7891504..6ddb76a85 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ UTF-8 2.1.7 + 1.0-SNAPSHOT 4.0.0.BUILD-SNAPSHOT @@ -132,22 +133,22 @@ - org.neo4j.ogm + org.neo4j neo4j-ogm - 1.0-SNAPSHOT + ${neo4j-ogm.version} - org.neo4j.ogm - neo4j-testutils - 1.0-SNAPSHOT + org.neo4j + neo4j-ogm-test + ${neo4j-ogm.version} test org.springframework.data spring-data-neo4j-tests - 4.0.0.BUILD-SNAPSHOT + ${sdn.version} test @@ -249,5 +250,4 @@ - diff --git a/spring-data-neo4j-docs/pom.xml b/spring-data-neo4j-docs/pom.xml new file mode 100644 index 000000000..03b863e95 --- /dev/null +++ b/spring-data-neo4j-docs/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + org.springframework.data + spring-data-neo4j-parent + 1.0-SNAPSHOT + + + neo4j-spring-docs + + Spring Data Neo4j Documentation + + + + process-resources + + + org.asciidoctor + asciidoctor-maven-plugin + 1.5.2 + + + asciidoc-to-html + generate-resources + + process-asciidoc + + + html5 + coderay + + + + + + + + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/index.adoc b/spring-data-neo4j-docs/src/main/asciidoc/index.adoc new file mode 100644 index 000000000..bbdc66389 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/index.adoc @@ -0,0 +1,109 @@ += Good Relationships: The Spring Data Neo4j Guide Book +Michael Hunger; Oliver Gierke; Vince Bickers; Adam George; Michal Bachman +:toc: +:toc-placement!: +:revnumber: {version} +:revdate: {localdate} +:idprefix: +:spring-data-commons-docs: https://raw.githubusercontent.com/spring-projects/spring-data-commons/issue/DATACMNS-567/src/main/asciidoc + +Spring Data Neo4j Version 4-M1 (Feb 2015) + +(C) 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. + +toc::[] + + +[[preface]] += Preface +:leveloffset: +1 +include::introduction/foreword.adoc[] +include::introduction/about.adoc[] +include::introduction/neo4j/neo4j.adoc[] + +:leveloffset: -1 + +//// +[[tutorial]] += Tutorial + +image::cineasts.png[] + +The first part of the book provides a tutorial that walks through the creation of a complete web application +called cineasts.net, built with Spring Data Neo4j. Cineasts are people who love movies, and the site is a gathering place +for moviegoers. For cineasts.net we decided to add a social aspect to the rating of movies, allowing friends to share +their scores and get recommendations for new friends and movies. + +The tutorial takes the reader through the steps necessary to create the application. It provides the configuration and +code examples that are needed to understand what's happening in Spring Data Neo4j. The complete source code for the app +is available on http://spring.neo4j.org/cineasts[Github]. + +:leveloffset: +1 +include::tutorial/about-tutorial.adoc[] +include::tutorial/setup.adoc[] +include::tutorial/domain.adoc[] +include::tutorial/neo4j.adoc[] +include::tutorial/spring-data-neo4j.adoc[] +include::tutorial/annotations.adoc[] +include::tutorial/indexing.adoc[] +include::tutorial/repository.adoc[] +include::tutorial/relationships.adoc[] +include::tutorial/running.adoc[] +include::tutorial/webapp.adoc[] +include::tutorial/social.adoc[] +include::tutorial/security.adoc[] +include::tutorial/user-experience.adoc[] +include::tutorial/import.adoc[] +include::tutorial/recommendations.adoc[] +include::tutorial/neo4j-server.adoc[] +include::tutorial/conclusion.adoc[] +:leveloffset: -1 + +//// + +[[reference]] += Spring Data Neo4j 4 Reference Documentation + +image::springdatagraph.png[] + +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. + +:leveloffset: +1 +include::reference/preface.adoc[] +include::reference/setup.adoc[] +include::reference/programming-model/programming-model.adoc[] +//// +include::reference/examples/samples.adoc[] +include::reference/deployment/heroku.adoc[] +//// +include::reference/performance/performance.adoc[] + +:leveloffset: -1 + +[[migration]] += Migrating from previous versions of Spring Data Neo4j + +:leveloffset: +1 +include::reference/migration/migration.adoc[] + +:leveloffset: -1 +//// +[[appendix]] += Appendix + +:leveloffset: +1 +include::{spring-data-commons-docs}/repository-namespace-reference.adoc[] +include::{spring-data-commons-docs}/repository-populator-namespace-reference.adoc[] +include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[] +:leveloffset: -1 +//// diff --git a/spring-data-neo4j-docs/src/main/asciidoc/index.html b/spring-data-neo4j-docs/src/main/asciidoc/index.html new file mode 100644 index 000000000..78587490f --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/index.html @@ -0,0 +1,2920 @@ + + + + + + + + +Good Relationships: The Spring Data Neo4j Guide Book + + + + + +
+
+
+
+

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. +
+
+
+
Table of Contents
+ +
+
+
+

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!

+
+
+
+— Rod Johnson
+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!

+
+
+
+— Emil Eifrem
+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.

+
+
+
Example 1. Cypher Examples on the Cineasts.net Dataset
+
+
+
+
// 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

+
+
+springdatagraph +
+
+
+

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

+
+
Example 2. Maven 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. +
+
+
+
Example 3. Pure Java bean configuration
+
+
+
+
@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:

+
+
+
    +
  1. +

    Annotated method (getter/setter)

    +
  2. +
  3. +

    Annotated field

    +
  4. +
  5. +

    Plain method (getter/setter)

    +
  6. +
  7. +

    Plain field

    +
  8. +
+
+
+

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.

+
+
+
Example 4. Persisting an annotated entity
+
+
+
+
@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.

+
+
+
Example 5. Persisting a non-annotated entity
+
+
+
+
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.Date to a String in the ISO 8601 format: "yyyy-MM-dd’T’HH:mm:ss.SSSXXX"

    +
  • +
  • +

    java.math.BigInteger to a String property

    +
  • +
  • +

    java.math.BigDecimal to a String property

    +
  • +
  • +

    binary data (as byte[] or Byte[]) to base-64 String

    +
  • +
  • +

    java.lang.Enum types using the enum’s name() method and Enum.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.

+
+
+
Example 6. @Graph on a node entity field
+
+
+
+
@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.

+
+
+
Example 7. Single relationship field
+
+
+
+
@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.

+
+
+
Example 8. Node entity with relationships
+
+
+
+
@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. +
+
+
+
Example 9. A simple Relationship entity
+
+
+
+
@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:

+
+
+
Example 10. Clashing Relationship Types
+
+
+
+
@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:

+
+
+
    +
  1. +

    Write a Cypher query to perform the operations on the nodes/relationships instead

    +
  2. +
  3. +

    Write a Neo4j server plugin and call it over REST from your application

    +
  4. +
+
+
+

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:

+
+
+
Example 11. Auditing Entities and Generating Unique Application-level IDs
+
+
+
+
@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) and Iterable<T> save(Iterable<T>)

+
+
Delete graph entities
+
+

void delete(T), void delete(Iterable<T>), and void 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

+
+
+
+
+
Examples of Cypher queries placed on repository methods with @Query where values are replaced with method parameters,
+

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 +
+
+
+
Example 12. Some examples of methods and corresponding Cypher queries of a PersonRepository
+
+
+
+
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 +
+
+
+
Example 13. Repository and usage of derived finder methods
+
+
+
+
@NodeEntity
+public static class Person {
+    @GraphId Long id;
+    private String name;
+    private Group group;
+
+    private Person(){}
+    public Person(String name) {
+        this.name = name;
+    }
+}
+
+@NodeEntity
+public static class Group {
+    @GraphId Long id;
+    private String title;
+    // incoming relationship for the person -> group
+    @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..

+
+
+
Example 14. Using basic GraphRepository methods
+
+
+
+
@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.

+
+
+
Example 15. Example Spring configuration bean
+
+
+
+
@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.Date to a String in the ISO 8601 format: "yyyy-MM-dd’T’HH:mm:ss.SSSXXX"

    +
  • +
  • +

    java.math.BigInteger to a String property

    +
  • +
  • +

    java.math.BigDecimal to a String property

    +
  • +
  • +

    binary data (as byte[] or Byte[]) to base-64 String

    +
  • +
  • +

    java.lang.Enum types using the enum’s name() method and Enum.valueOf()

    +
  • +
+
+
+

Two Date converters are provided "out of the box"

+
+
+
    +
  1. +

    @DateString

    +
  2. +
  3. +

    @DateLong

    +
  4. +
+
+
+

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:

+
+
+
Example 16. Example of user-defined date format
+
+
+
+
---
+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.

+
+
+
Example 17. Example of custom type converter
+
+
+
+
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.

+
+
+
Example 18. Example of query result mapping
+
+
+
+
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.

+
+
+
Example 19. Neo4j Spring integration
+
+
+
+
<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 +
+
+
+
Example 20. ChainedTransactionManager example
+
+
+
+
<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.

+
+
+
Example 21. Persisting entities
+
+
+
+
@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. +
+
+
+
Example 22. Relationship save cascading
+
+
+
+
@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:

+
+
+
Example 23. Cascade for modified fields
+
+
+
+
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.

+
+
+
Example 24. Example domain model and labels
+
+
+
+
@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.

+
+
+
Example 25. Bean validation
+
+
+
+
@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:

+
+
+
Example 26. Validation setup
+
+
+
+
<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:

+
+
+
    +
  1. +

    org.springframework.data.neo4j.annotationorg.neo4j.ogm.annotation

    +
  2. +
+
+
+

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.

+
+
+
    +
  1. +

    @RelatedTo@Relationship

    +
  2. +
  3. +

    @RelatedToVia@Relationship

    +
  4. +
  5. +

    @GraphProperty@Property

    +
  6. +
  7. +

    Relationship Direction.BOTHRelationship.UNDIRECTED

    +
  8. +
+
+
+
+

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.

+
+
+
    +
  1. +

    @GraphTraversal

    +
  2. +
  3. +

    @RelatedToVia

    +
  4. +
  5. +

    @Index

    +
  6. +
  7. +

    @TypeAlias

    +
  8. +
+
+
+
+

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.

+
+
+
Example 27. Clashing Relationship Types
+
+
+
+
@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

+
+
    +
  1. +

    Geospatial queries

    +
  2. +
  3. +

    @QueryResult

    +
  4. +
+
+
+
+ + + \ No newline at end of file diff --git a/spring-data-neo4j-docs/src/main/asciidoc/introduction/about.adoc b/spring-data-neo4j-docs/src/main/asciidoc/introduction/about.adoc new file mode 100644 index 000000000..8a45c6f4d --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/introduction/about.adoc @@ -0,0 +1,35 @@ +[preface] += 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 http://spring.neo4j.org[Spring Data Neo4j]. This project is part of the http://springsource.org/spring-data[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 http://neo4j.org[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 http://spring.neo4j.org/issues[SpringSource JIRA], the http://spring.neo4j.org/discussion[SpringSource NOSQL Forum], http://github.com/spring-projects/spring-data-neo4j/issues[github comments or issues], or the http://neo4j.org/forums/[Neo4j mailing list]. + +== Format of the Book + +This book is presented as a http://martinfowler.com/bliki/DuplexBook.html[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! + + + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/introduction/foreword.adoc b/spring-data-neo4j-docs/src/main/asciidoc/introduction/foreword.adoc new file mode 100644 index 000000000..84ef9fafa --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/introduction/foreword.adoc @@ -0,0 +1,32 @@ +[preface] +[[foreword]] += Foreword + +[quote, Rod Johnson, Founder of the Spring Framework] +____ +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! +____ + +[quote, Emil Eifrem, CEO of Neo Technology] +____ +"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! +____ + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/introduction/neo4j/neo4j-server.adoc b/spring-data-neo4j-docs/src/main/asciidoc/introduction/neo4j/neo4j-server.adoc new file mode 100644 index 000000000..f663e71c0 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/introduction/neo4j/neo4j-server.adoc @@ -0,0 +1,131 @@ +[[reference_neo4j-server]] += Neo4j Server + +Neo4j is not only available in embedded mode. It can also be installed and run as a stand-alone server accessible via a HTTP API. +Developers can integrate Spring Data Neo4j into the Neo4j server infrastructure in two ways: as a server extension, or remotely via the HTTP API. + +Spring Data Neo4j was historically built around the Neo4j embedded Java APIs, but are not optimized for remote usage. + Most of the Graph Database operations are sent as Cypher statements to the server's transactional Cypher endpoint. + Only the few operations that are not supported by Cypher yet (legacy indexes, traversals, management operations) use the Neo4j Server REST API. + +== Server Extension + +When would you write a server extension? + If you want to achieve the performance, that you get from the embedded Neo4j usage in Spring Data Neo4j, then a server extension is the easiest way. + Running as a extension within Neo4j Server, Spring Data Neo4j can access the Neo4j the same way as running with a embedded database inside your Spring application. + +The Neo4j Server has two built-in extension mechanisms. It is possible to extend existing REST endpoints for the graph database, nodes, or relationships, adding new service URIs or methods to those. + This is achieved by writing a http://neo4j.com/docs/stable/server-plugins.html[server plugin]. However this approach has some restrictions in terms of HTTP verbs and result types. + +For an unrestricted implementation, an http://neo4j.com/docs/stable/server-unmanaged-extensions.html[unmanaged extension] can be used. Unmanaged extensions are essentially http://jersey.java.net/[Jersey] resource implementations. +The resource constructors or methods can get `@Context GraphDatabaseService` and `@Context CypherExecutor` instances injected to run the necessary Neo4j API calls and Cypher statements and return appropriate `Representations`. + +Both kinds of extensions have to be packaged as JAR files and added to the Neo4j Server's plugin directory. Server Plugins are picked up by the server at startup if they provide the necessary `META-INF.services/org.neo4j.server.plugins.ServerPlugin` file for Java's ServiceLoader facility. Unmanaged extensions have to be registered with the Neo4j Server configuration in `conf/neo4j-wrapper.conf`. + +.Configuring an unmanaged extension +==== +[source,ini] +---- +org.neo4j.server.thirdparty_jaxrs_classes=com.example.mypackage=/my-context +---- +==== + +Integrating Spring Data Neo4j ApplicationContext configuration in the Neo4j Server is easy. +You provide the Spring context configuration location, and list which Spring-beans should be exposed: + +.Server plugin initialization +==== +[source,java] +---- +public class HelloWorldInitializer extends SpringPluginInitializer { + public HelloWorldInitializer() { + super(new String[]{"spring/helloWorldServer-Context.xml"}, + Pair.of("worldRepository", WorldRepository.class), + Pair.of("template", Neo4jTemplate.class)); + } +} +---- +==== + +Now, your resources can require the Spring beans they need as parameters, annotated with `@Context`: + +.Jersey resource +==== +[source,java] +---- +@Path( "/path" ) +@POST +@Produces( MediaType.APPLICATION_JSON ) +public void foo( @Context WorldRepository repo ) { + ... +} +---- +==== + +The `SpringPluginInitializer` merges the server provided `GraphDatabaseService` with the Spring configuration and registers the named beans as Jersey `Injectables`. + It is still necessary to list the `SpringPluginInitializer` implementation's fully qualified class name in a file named `META-INF/services/org.neo4j.server.plugins.PluginLifecycle`, e.g. `org.example.extension.HelloWorldInitializer`. + The Neo4j Server can then pick up and run the initialization classes before the extensions are loaded. + +== Using Spring Data Neo4j as a Neo4j Server client + +To use Neo4j's remote APIs, you can use them directly to send Cypher statements to the server, e.g. with the http://neo4j.com/developer/java/#_using_spring_boot_with_jdbc[Neo4j-JDBC] driver. + That JDBC driver integrates well with the commonly used `spring-jdbc` libraries and classes. + +There are also other http://neo4j.com/developer/language-guides[remote drivers] for Neo4j available. + +Spring Data Neo4j's integration with the server also uses the Cypher endpoint to execute GraphDatabaseService operations transactionally against the server. + The implementation of the integration is handled by `SpringCypherRestGraphDatabase` and `RestAPICypherImpl` which wraps the _old_ REST-API methods in the appropriate Cypher statement calls. + It integrates with the Spring Transaction APIs by providing a `javax.transaction.TransactionManager` implementation that is configured to be used by the `JtaTransactionManager` bean provided by Spring Data Neo4j. + +By simply configuring the `graphDatabaseService` to be a `SpringCypherRestGraphDatabase` pointing to a Neo4j Server instance and referring to that from ``, Spring Data Neo4j will use the server side database for both the simple mapping as well as the advanced mapping. + +NOTE: The Neo4j Server REST API does not allow for transactions to span across requests, which means that all operations that are not handled by Cypher (traversals, legacy index lookups and management operations) are not participating in the Cypher transactions. + +Please also keep in mind that performing graph operations via the remote API is slower than local operations. + You have to take roundtrip latency, request serialization and result parsing into account. + Also remember that Spring Data Neo4j was built around Neo4j's embedded APIs, that's why it is not the most efficient user of the remote API. + A new version of a Java OGM and Spring Data Neo4j is in development that addresses these issues. + +If the mapping CRUD operations are too slow, try to avoid the automatic fetching of additional levels of entities. +Basic CRUD should be fast enough. +Use Cypher to execute operations within the server and map the results using `@QueryResult` POJOs or interfaces. + +To set up your project to use the remote Neo4j Server integration, add this dependency to your `pom.xml`: + +.Remote Client configuration - pom.xml +==== +[source,xml] +---- + + org.springframework.data + spring-data-neo4j-rest + 3.3.0.M1 + +---- +==== + +Now, you set up the normal Spring Data Neo4j configuration, but point the database instance to an URL instead of a local directory: + +.Remote configuration - application context +==== +[source,xml] +---- + + + + + + +---- +==== + +Your project is now set up to work with a remote Neo4j Server. + +For direct execution of Cypher graph queries and graph traversals it is sensible to forward those to the remote side and execute them on the server. + `SpringCypherRestGraphDatabase` already supports this approach by providing appropriate methods. (e.g. `query()`, `queryEngineFor(), index()` and `createTraversalDescription()`). + Please use those methods when interacting with a remote server for better performance. + Those methods are also used by the Neo4jTemplate and the mapping infrastructure implementation. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/introduction/neo4j/neo4j.adoc b/spring-data-neo4j-docs/src/main/asciidoc/introduction/neo4j/neo4j.adoc new file mode 100644 index 000000000..c67650e2b --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/introduction/neo4j/neo4j.adoc @@ -0,0 +1,98 @@ +[[neo4j]] += 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 + +http://neo4j.org/[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 http://neo4j.com/docs/stable/what-is-a-graphdb.html[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 http://api.neo4j.org/[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 http://neo4j.com/docs/stable/cypher-query-lang.html["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. + +.Cypher Examples on the Cineasts.net Dataset +==== +[source] +---- +// 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 http://neo4j.com/docs/stable/query-schema-index.html[Neo4j Documentation] +on indices. + + + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/redundant/cross-store.adoc b/spring-data-neo4j-docs/src/main/asciidoc/redundant/cross-store.adoc new file mode 100644 index 000000000..af1f5ebff --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/redundant/cross-store.adoc @@ -0,0 +1,119 @@ +[[reference_cross-store]] += Cross-store persistence + +The Spring Data Neo4j project support cross-store persistence for the advanced mapping mode, which allows for parts of the data to be stored in a traditional JPA data store (RDBMS), and other parts in a graph store. This means that an entity can be partially stored in e.g. MySQL, and partially stored in Neo4j. + +This allows existing JPA-based applications to embrace NOSQL data stores for evolving certain parts of their data model. Possible use cases include adding social networking or geospatial information to existing applications. + +== Partial entities + +Partial graph persistence is achieved by restricting the Spring Data Neo4j aspects to manage only explicitly annotated parts of the entity. Those fields will be made `@Transient` by the aspect so that JPA ignores them. + +A backing node in the graph store is only created when the entity has been assigned a JPA ID. Only then will the association between the two stores be established. Until the entity has been persisted, its state is just kept inside the POJO (in detached state), and then flushed to the backing graph database on the persist operation. + +The association between the two entities is maintained via a FOREIGN_ID field in the node, that contains the JPA ID. Currently only single-value IDs are supported. The entity class can be resolved via the `TypeRepresentationStrategy` that manages the Java type hierarchy within the graph database. Given the ID and class, you can then retrieve the appropriate JPA entity for a given node. + +The other direction is handled by indexing the Node with the FOREIGN_ID index which contains a concatenation of the fully qualified class name of the JPA entity and the ID. The matching node can then be found using the indexing facilities, and the two entities can be reassociated. + +Using these mechanisms and the Spring Data Neo4j aspects, a single POJO can contain some fields handled by JPA and others handles by Spring Data Neo4j. This also includes relationship fields persisted in the graph database. + +== Cross-store annotations + +Cross-store persistence only requires the use of one additional annotation: `@GraphProperty`. See below for details and an example. + +=== @NodeEntity(partial = "true") + +When annotating an entity with `partial = true`, this marks it as a cross-store entity. Spring Data Neo4j will thus only manage fields explicitly annotated with `@GraphProperty`. + +=== @GraphProperty + +Fields of primitive or convertible types do not normally have to be annotated in order to be persisted by Spring Data Neo4j. In cross-store mode, Spring Data Neo4j *only* persists fields explicitly annotated with `@GraphProperty`. JPA will ignore these fields. + +=== Example + +The following example is taken from the http://spring.neo4j.org/examples[Spring Data Neo4j examples] myrestaurants-social project: + +.Cross-store node entity +==== +[source,java] +---- +@Entity +@Table(name = "user_account") +@NodeEntity(partial = true) +public class UserAccount { + private String userName; + private String firstName; + private String lastName; + + @GraphProperty + String nickname; + + @RelatedTo + Set friends; + + @RelatedToVia(type = "recommends") + Iterable recommendations; + + @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(style = "S-") + private Date birthDate; + + @ManyToMany(cascade = CascadeType.ALL) + private Set favorites; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Long id; + + public void knows(UserAccount friend) { + relateTo(friend, "friends"); + } + + public Recommendation rate(Restaurant restaurant, int stars, String comment) { + Recommendation recommendation = relateTo(restaurant, Recommendation.class, "recommends"); + recommendation.rate(stars, comment); + return recommendation; + } + + public Iterable getRecommendations() { + return recommendations; + } +} +---- +==== + +== Configuring cross-store persistence + +Configuring cross-store persistence is done similarly to the default Spring Data Neo4j configuration. All you need to do is to specify an `entityManagerFactory` in the XML namespace `config` element, and Spring Data Neo4j will configure itself for cross-store use. + +.Cross-store Spring configuration +==== +[source,xml] +---- + + + + + + + + + + + +---- +==== diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/deployment/heroku.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/deployment/heroku.adoc new file mode 100644 index 000000000..da5fb8746 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/deployment/heroku.adoc @@ -0,0 +1,193 @@ +[[reference_heroku]] += Heroku: Seeding the Cloud + +Deploying your application into the cloud is a great way to scale from from "wouldn't it be cool if.." to giving interviews to Forbes, Fast Company, and Jimmy Fallon. Heroku makes it super easy to provision everying you need, including a Neo4j Add-on. With a few simple adjustments, your Spring Data Neo4j application is ready to take that first step into the cloud. + +To deploy your Spring Data Neo4j web application to Heroku, you'll need: + +* account on http://heroku.com[Heroku] +* git command line +* maven-based project +* standard Spring MV Servlet application +* well, and Spring Data Neo4j REST + +For reference, the following sections detail the steps taken to make the Spring Data Neo4j Todos example ready for deployment to Heroku. + +== Create a Self-Hosted Web Application + +Usually, a Spring MVC application is bundled into a war and deployed to an application server like Tomcat. But Heroku can host any kind of java application. It just needs to know what to launch. So, we'll transform the war into a self-hosted servlet using an embedded Jetty server, then add a startup script to launch it. + +First, we'll add the dependencies for Jetty to the `pom.xml`: + +.Jetty dependencies - pom.xml +==== +[source,xml] +---- + + org.eclipse.jetty + jetty-webapp + 7.4.4.v20110707 + + + org.mortbay.jetty + jsp-2.1-glassfish + 2.1.v20100127 + +---- +==== + +Then we'll change the scope of the servlet-api artifact from `provided` to `compile`. This library is normally provided at runtime by the application container. Since we're self-hosting, it needs to be included directly. Make sure the servlet-api dependency looks like this: + +.servlet-api dependencies - pom.xml +==== +[source,xml] +---- + + javax.servlet + servlet-api + 2.5 + compile + +---- +==== + +We could provide a complicated command-line to Heroku to launch the app. Instead, we'll simplify the command-line by using the `appassembler-maven-plugin` to create a launch script. Add the plugin to your pom's `build/plugins` section: + +.appassembler-maven-plugin configuration pom.xml +==== +[source,xml] +---- + +org.codehaus.mojo +appassembler-maven-plugin +1.1.1 + + + package + assemble + + target + -Xmx512m + + + Main + webapp + + + + + + +---- +==== + +Finally, switch the packaging from `war` to `jar`. That's it for the pom. + +Now that the application is ready to be self-hosted, create a simple `Main` to bootstrap Jetty and host the servlet. + +.src/main/java/Main.java +==== +[source,java] +---- +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.webapp.WebAppContext; +public class Main { + public static void main(String[] args) throws Exception { + String webappDirLocation = "src/main/webapp/"; + String webPort = System.getenv("PORT"); + if(webPort == null || webPort.isEmpty()) { + webPort = "8080"; + } + Server server = new Server(Integer.valueOf(webPort)); + WebAppContext root = new WebAppContext(); + root.setContextPath("/"); + root.setDescriptor(webappDirLocation+"/WEB-INF/web.xml"); + root.setResourceBase(webappDirLocation); + root.setParentLoaderPriority(true); + server.setHandler(root); + server.start(); + server.join(); + } +} +---- +==== + +Notice the use of environment variable "PORT" for discovering which port to use. Heroku and the Neo4j Add-on use a number of environment variable to configure the application. Next, we'll modify the Spring application context to use the Neo4j variables for specifying the connection to Neo4j itself. + +In the SDN Todos example, `src/main/resources/META-INF/spring/applicationContext-graph.xml` was modified to look like this: + +.Spring Data Neo4j REST configuration - applicationContext-graph.xml +==== +[source,xml] +---- + + + + + + +---- +==== + +Before provisioning at Heroku, test the application locally. First make sure you've got Neo4j server running locally, using default configuration. Then set the following environment variables: + +.environment variables +==== +[source] +---- +export NEO4J_REST_URL=http://localhost:7474/db/data +export NEO4J_LOGIN="" +export NEO4J_PASSWORD="" +---- +==== + +Now you can launch the app by running `sh target/bin/webapp`. If running the SDN Todos example, you can test it by running `./bin/todos list`. That should return an empty JSON array, since no todos have been created yet. + +For details about the `todos` script, see the `readme` included with the example. + +== Deploy to Heroku + +With a self-hosted application ready, deploying to Heroku needs a few more steps. First, create a `Procfile` at the top-level of the project, which will contain a single line identifying the command line which launches the application. + +The contents of the `Procfile` should contain: + +.Procfile +==== +[source] +---- +web: sh target/bin/webapp +---- +==== + +.deploy to heroku +==== +[source] +---- +# Initialize a local git repository, adding all the project files + git init + git add . + git commit -m "initial commit" + +# Provision a Heroku stack, add the Neo4j Add-on and deploy the appication + + heroku create --stack cedar + heroku addons:add neo4j + git push heroku master +---- +==== + +NOTE: Note that the stack must be "cedar" to support running Java. Check that the process is running by using `heroku ps`, which should show a "web.1" process in the "up" state. Success! + +For the SDN Todos application, you can try out the remote application using the `-r` switch with the `bin/todo` script like this: + +.Session with todo script +==== +[source] +---- +./bin/todo -r mk "tweet thanks for the good work @mesirii @akollegger" +./bin/todo -r list +---- +==== + +To see the Neo4j graph you just created through Heroku, use `heroku config` to reveal the `NEO4J_URL` environment variable, which will take you to Neo4j's Webadmin. diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/helloworlds/helloworlds-labeltrs.png b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/helloworlds/helloworlds-labeltrs.png new file mode 100644 index 000000000..0240bd536 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/helloworlds/helloworlds-labeltrs.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/helloworlds/helloworlds-old.png b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/helloworlds/helloworlds-old.png new file mode 100644 index 000000000..9d517ce2b Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/helloworlds/helloworlds-old.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/helloworlds/helloworlds-subreftrs.png b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/helloworlds/helloworlds-subreftrs.png new file mode 100644 index 000000000..acd2c32fa Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/helloworlds/helloworlds-subreftrs.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/imdb/imdb.png b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/imdb/imdb.png new file mode 100644 index 000000000..1a8dc6c41 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/imdb/imdb.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/myrestaurants/restaurant-social-graph.png b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/myrestaurants/restaurant-social-graph.png new file mode 100644 index 000000000..019467be2 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/myrestaurants/restaurant-social-graph.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/myrestaurants/restaurant-social.png b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/myrestaurants/restaurant-social.png new file mode 100644 index 000000000..088413ebc Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/myrestaurants/restaurant-social.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/myrestaurants/restaurant.png b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/myrestaurants/restaurant.png new file mode 100644 index 000000000..b749a68c8 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/myrestaurants/restaurant.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/samples.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/samples.adoc new file mode 100644 index 000000000..94b9eb744 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/examples/samples.adoc @@ -0,0 +1,79 @@ +[[reference_samples]] += Sample code + +[[samples_introduction]] +== Introduction + +Spring Data Neo4j comes with a number of sample applications. The source code of the samples can be found on http://spring.neo4j.org/exampless[Github]. The different sample projects are introduced below. + +[[samples_hello-worlds]] +== Hello Worlds sample application + +The Hello Worlds sample application exists merely to provide a client with a way of creating some "worlds" (node entities) and "rocket routes" (relationships) between worlds, all in a galaxy (the graph). There is currently no GUI for this application, rather you can have a look at the associated unit tests for some examples of how to interact with this domain via a dedicated `GalaxyService` class. + +The unit tests additionally demonstrate some other features of Spring Data Neo4j as well. The sample comes with a minimal configuration for Maven and Spring to get up and running quickly. + +The Hello Worlds application is available both for the simple mapping (`hello-worlds`) and for the advanced mapping (`hello-world-aspects`). + +Running the unit tests create two versions of the world, one based on the default Label based Type Representation Strategy, and another (for comparison purposes) based on the Sub Reference type representation strategy. From the `GalaxyService` clients perspective, there is no difference, however how the data physically stored in the graph, does differ, and we take this opportunity to show you how using two different type representation strategies can impact how your data is modelled in the graph itself. + +Hello World Galaxy - Using default "Label" Type Representation Strategy + +image::helloworlds-labeltrs.png[] + +Hello World Galaxy - Using "Sub Reference" Type Representation Strategy + +image::helloworlds-subreftrs.png[] + +[[samples_imdb]] +== IMDB sample application + +NOTE: This sample application still needs to be upgraded to SDN 3.0.X + + The IMDB sample is a web application that imports datasets from the Internet Movie Database (IMDB) into the graph database. It allows the listing of movies with their actors, and of actors and their roles in different movies. It also uses graph traversal operations to calculate the http://en.wikipedia.org/wiki/Bacon_number[Bacon number] of any given actor. This sample application shows the usage of Spring Data Neo4j in a more complex setting, using several annotated entities and relationships as well as indexes and in-graph indexes and graph traversals. + +See the readme file for instructions on how to compile and run the application. + +An excerpt of the data stored in the graph database after executing the application: + +image::imdb.png[] + +[[samples_myrestaurants-original]] +== MyRestaurants sample application + +NOTE: This sample application still needs to be upgraded to SDN 3.0.X + +Simple, JPA-based web application for managing users and restaurants, with the ability to add restaurants as favorites to a user. It is basically the foundation for the MyRestaurants-Social application (see<>), and does therefore not use Spring Data Neo4j. + +image::restaurant.png[] + +[[samples_myrestaurants-social]] +== MyRestaurant-Social sample application + +NOTE: This sample application still needs to be upgraded to SDN 3.0.X + +This application extends the MyRestaurants sample application, adding social networking functionality to it with cross-store persistence. The web application allows for users to add friends and rate restaurants. A graph traversal provides recommendations based on your friends' (and their friends') rating of restaurants. + +Here's an excerpt of the data stored in the graph database after executing the application: + +image::restaurant-social.png[] + +image::restaurant-social-graph.png[] + +[[samples_cineasts]] +== Cineasts social movie database + +NOTE: This sample application still needs to be upgraded to SDN 3.0.X + +The cineasts.net application was introduced extensively in the first part of this guide, the tutorial. The tutorial covers the development of the simple mapping version of cineasts. + +To document the differences, versions for the advanced mapping (`cineasts-aspects`) and accessing the remote server (`cineasts-rest`) are also available. + +A online version of cineasts can be found on http://cineasts.net[cineasts.net]. A sample dataset of the cineasts databse is available at the neo4j http://sample-data.neo4j.org[sample-data page]. + +This is a subset of the visualization of the cineasts graph for the "Matrix" movie. + +image::../tutorial/cineasts_graph.png[] + +image::../tutorial/cineasts_main.png[] + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/migration/migration.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/migration/migration.adoc new file mode 100644 index 000000000..94985b253 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/migration/migration.adoc @@ -0,0 +1,98 @@ +:numbered: +== 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. + +== 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` + +[[custom_converters]] +== 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: <> + +== 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 +. @RelatedTo +. @Index +. @TypeAlias +. @Fetch + +== Features no longer supported + +Some features of the previous annotations have been dropped. + +=== 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 <> instead. + +=== @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. + +.Clashing Relationship Types +==== +[source,java] +---- +@NodeEntity +class Person { + @Relationship(type="OWNS") + private Car car; + + @Relationship(type="OWNS") + private Pet pet; +... +} +---- +==== + +=== Cross-store persistence +Neo4j is dropping XA support and therefore SDN 4 does not provide any capability for cross-store persistence + +=== 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. + +=== Graph traversal and node/relationship manipulation with Neo4jTemplate +These features cannot be supported by Cypher and have therefore been dropped from Neo4jTemplate. + +=== 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 <> for more details. + +=== 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. + +== Features not yet included in Milestone 1 but that are on the Roadmap + +. Geospatial queries +. @QueryResult + +:numbered!: diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/performance/performance.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/performance/performance.adoc new file mode 100644 index 000000000..a318aecec --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/performance/performance.adoc @@ -0,0 +1,66 @@ +[[reference_performance]] += 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. + + + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/preface.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/preface.adoc new file mode 100644 index 000000000..cb138ac9a --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/preface.adoc @@ -0,0 +1,72 @@ +[[reference_preface]] += About the Spring Data project + +http://springsource.org/spring-data[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 <> to avoid any unwanted surprises. + +For integration of Neo4j and other languages, please see http://neo4j.com/developer/language-guides/[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 <>) 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 <>. 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 +(<>) 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. (<>). + +== Neo4jTemplate +Being a Spring Data library, Spring Data Neo4j still offers a `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: <>. + +== 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. (<>) + +== Configuration +As of Milestone 1, only Java bean-based configuration is supported. See <> for more details. + +== Examples +The provided samples, which are also publicly hosted on http://spring.neo4j.org/examples[Github], are explained +in <>. + +== Performance +Spring Data Neo4j 4 has been rebuilt from the ground up with performance in mind. More information can be found in +<>. This chapter also discusses which use cases should not be handled with Spring Data Neo4j. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/attachdetach.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/attachdetach.adoc new file mode 100644 index 000000000..863e1e726 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/attachdetach.adoc @@ -0,0 +1,89 @@ +[[reference_programming_model_lifecycle]] += 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. + + +[[reference_programming-model_lifecycle_persistence]] +== 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. + + +.Persisting entities +==== +[source,java] +---- +@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); +---- +==== + +[[reference_programming-model_detached_relating]] +== 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. + +.Relationship save cascading +==== +[source,java] +---- +@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: + +.Cascade for modified fields +==== +[source,java] +---- +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. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/beanvalidation.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/beanvalidation.adoc new file mode 100644 index 000000000..1c2b4acce --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/beanvalidation.adoc @@ -0,0 +1,37 @@ +[[reference_programming_model_validation]] += 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. + +.Bean validation +==== +[source,java] +---- +@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: + +.Validation setup +==== +[source,xml] +---- + + org.hibernate + hibernate-validator + 4.2.0.Final + + +---- +==== diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/conversion.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/conversion.adoc new file mode 100644 index 000000000..df47424fa --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/conversion.adoc @@ -0,0 +1,126 @@ +[[reference_programming_model_conversion]] += 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. + +[[reference_programming-model_conversion-built_in]] +== Built-In Type Conversions + +By default, Spring Data Neo4j will automatically perform the following type conversions: + +- `java.util.Date` to a String in the ISO 8601 format: "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" +- `java.math.BigInteger` to a String property +- `java.math.BigDecimal` to a String property +- binary data (as byte[] or Byte[]) to base-64 String +- `java.lang.Enum` types using the enum's `name()` method and `Enum.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: + +.Example of user-defined date format +==== +[source, java] +--- +public class MyEntity { + + @DateString("yy-MM-dd") + private Date entityDate; +} +--- +=== + +Alternatively, if you want to store Dates as long values, use the @DateLong annotation: + +.Example of date stored as a long value +==== +[source, java] +--- +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. + +[[reference_programming-model_conversion-custom]] +== 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. + +.Example of custom type converter +==== +[source,java] +---- +public class MoneyConverter implements AttributeConverter { + + @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: + +==== +[source,java] +---- +@NodeEntity +public class Invoice { + + @Convert(MoneyConverter.class) + private DecimalCurrencyAmount value; + ... +} +---- +==== + +[[reference_programming-model_mapresult]] +== 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. + +.Example of query result mapping +==== +[source,java] +---- +public interface MovieRepository extends GraphRepository { + + @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 cast; + } + +} +---- +==== + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/indexing.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/indexing.adoc new file mode 100644 index 000000000..14aa4b54a --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/indexing.adoc @@ -0,0 +1,27 @@ +[[reference_programming_model_indexing]] += 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. + +== 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 + +== 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. + +== Neo4j Auto Indexes + +Neo4j allows to configure (legacy) http://neo4j.com/docs/stable/auto-indexing.html[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. + +== 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. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/mapping.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/mapping.adoc new file mode 100644 index 000000000..ff441ac9c --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/mapping.adoc @@ -0,0 +1,54 @@ +[[reference_programming_model_mapping]] += Under the hood + +== 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`. + +== 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. + +== 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. + +== 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. + +=== 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. + +=== 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. + + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/node-entities.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/node-entities.adoc new file mode 100644 index 000000000..653ccc2ba --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/node-entities.adoc @@ -0,0 +1,140 @@ +[[reference_programming_model_annotations]] += Defining node entities + +Node entities are declared using the `@NodeEntity` annotation. Relationship entities use the `@RelationshipEntity` annotation. + +== @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. + + +.Persisting an annotated entity +==== +[source,java] +---- +@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 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. + +.Persisting a non-annotated entity +==== +[source,java] +---- +public class Actor extends DomainObject { + + private Long id; + private String fullName; + private List 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. + + +== @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. + + +=== 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. + + +== @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.Date` to a String in the ISO 8601 format: "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" +- `java.math.BigInteger` to a String property +- `java.math.BigDecimal` to a String property +- binary data (as byte[] or Byte[]) to base-64 String +- `java.lang.Enum` types using the enum's `name()` method and `Enum.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 <>. + +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. + +== @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`. 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. + +.@Graph on a node entity field +==== +[source,java] +---- +@NodeEntity +public class Group { + @Query(value = "MATCH ({self})-[r]->(friend) WHERE r.type={relType} RETURN friend", + params = {"relType", "FRIEND_OF"}) + private Iterable friends; +} +---- +==== + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/programming-model.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/programming-model.adoc new file mode 100644 index 000000000..4dcf5bb9b --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/programming-model.adoc @@ -0,0 +1,20 @@ +[[reference_programming-model]] += 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. + +:numbered: +include::mapping.adoc[] +include::simple_mapping.adoc[] +include::node-entities.adoc[] +include::relationships.adoc[] +include::indexing.adoc[] +include::template.adoc[] +include::repositories.adoc[] +include::conversion.adoc[] +include::transactions.adoc[] +include::attachdetach.adoc[] +include::typerepresentationstrategy.adoc[] +include::beanvalidation.adoc[] +:numbered!: \ No newline at end of file diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/relationships.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/relationships.adoc new file mode 100644 index 000000000..5f70139cc --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/relationships.adoc @@ -0,0 +1,122 @@ +[[reference_programming_model_relationships]] += 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. + +== @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. + +.Single relationship field +==== +[source,java] +---- +@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`, and read-only fields are `Iterable`, where T is a type annotated with `@NodeEntity`. + +.Node entity with relationships +==== +[source,java] +---- +@NodeEntity +public class Actor { + @Relationship(type = "TOP_ACTOR", direction = Relationship.INCOMING) + private Set topActorIn; + + @Relationship(type = "ACTS_IN") + private Set 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.. + + +== @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. + +.A simple Relationship entity +==== +[source,java] +---- +@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. + + +[[reference_programming_model_relationships_relationshiptypediscrimination]] +== 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: + +.Clashing Relationship Types +==== +[source,java] +---- +@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. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/repositories.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/repositories.adoc new file mode 100644 index 000000000..257353eee --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/repositories.adoc @@ -0,0 +1,250 @@ +[[reference_programming_model_repositories]] += CRUD with repositories + +The repositories provided by Spring Data Neo4j build on the composable repository infrastructure in http://static.springsource.org/spring-data/data-commons/docs/current/reference/html/#repositories[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` 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. + + +== GraphRepository + +As of SDN 4, this `GraphRepository` 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: <> + +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 findAll()` `Iterable findAll(Sort)` + +Count the instances of the repository entity type:: +`Long count()` + +Save entities:: +`T save(T)` and `Iterable save(Iterable)` + +Delete graph entities:: +`void delete(T)`, `void delete(Iterable)`, and `void deleteAll()` + +== Query and Finder Methods + +[[reference_programming-model_annotatedQueries]] +=== 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 + + +=== Query results + +Typical results for queries are `Iterable`, `Iterable>` or `Type`. As of milestone one, +`Slice` and `Page` are not supported. Nodes and relationships are converted to their respective entities +(if they exist). Other values are converted using the registered <> (e.g. enums). + +=== 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 + +.Examples of Cypher queries placed on repository methods with @Query where values are replaced with method parameters, +as described in the <>) section. +==== +[source,java] +---- +public interface MovieRepository extends GraphRepository { + + // 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 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 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 getUsersWhoRatedMovieFromTitle(String movieTitle, Integer rating); + } +---- +==== + +=== 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 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 + +.Some examples of methods and corresponding Cypher queries of a PersonRepository +==== +[source,java] +---- +public interface PersonRepository extends GraphRepository { + + // MATCH (person:Person {name={0}}) RETURN person + Person findByName(String name); + + // MATCH (person:Person) WHERE person.name =~ '.*{0}.*' RETURN person + Iterable findByNameLike(String name) + + // MATCH (person:Person) + // WHERE person.age = {0} AND person.married = {1} + // RETURN person + Iterable findByAgeAndMarried(int age, boolean married) + +} +---- +==== + +=== 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 + +.Repository and usage of derived finder methods +==== +[source,java] +---- +@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 members = new HashSet<>(); + + private Group(){} + public Group(String title, Person... people) { + this.title = title; + members.addAll(asList(people)); + } +} +public interface PersonRepository extends GraphRepository { + Iterable 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 members = personRepository.findByGroupTitle("spring-data"); +assertThat(members.iterator().next().name, is(oliver.name)); +---- +==== + +== 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.. + +.Using basic GraphRepository methods +==== +[source,java] +---- +@Repository +public interface PersonRepository extends GraphRepository {} + +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 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. + +.Example Spring configuration bean +==== +[source,java] +---- +@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 +} +---- +==== + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/simple_mapping.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/simple_mapping.adoc new file mode 100644 index 000000000..7c9496f2a --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/simple_mapping.adoc @@ -0,0 +1,27 @@ +[[reference_programming_model_simple-mapping]] += 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 <>. + + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/template.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/template.adoc new file mode 100644 index 000000000..c79fdf419 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/template.adoc @@ -0,0 +1,94 @@ +[[reference_programming_model_template]] += 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. + +== 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. + + +== 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. + +== 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>` 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. + +== 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`. + +== 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: + +.Auditing Entities and Generating Unique Application-level IDs +==== +[source,java] +---- +@Configuration +@EnableNeo4jRepositories +public class ApplicationConfig extends Neo4jConfiguration { + ... + @Bean + ApplicationListener beforeSaveEventApplicationListener() { + return new ApplicationListener() { + @Override + public void onApplicationEvent(BeforeSaveEvent event) { + AcmeEntity entity = (AcmeEntity) event.getEntity(); + entity.setUniqueId(acmeIdFactory.create()); + } + }; + } + + @Bean + ApplicationListener afterSaveEventApplicationListener() { + return new ApplicationListener() { + @Override + public void onApplicationEvent(AfterSaveEvent event) { + AcmeEntity entity = (AcmeEntity) event.getEntity(); + auditLog.onEventSaved(entity); + } + }; + } + + @Bean + ApplicationListener deleteEventApplicationListener() { + return new ApplicationListener() { + @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. diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/transactions.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/transactions.adoc new file mode 100644 index 000000000..1e29d476f --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/transactions.adoc @@ -0,0 +1,71 @@ +[[reference_programming_model_transactions]] += 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. + +.Neo4j Spring integration +==== +[source,xml] +---- + + + + + + + + + + + + + + + + + + + +---- +==== + +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 + +.ChainedTransactionManager example +==== +[source,xml] +---- + + + + + + + + + + + + + + + + +---- +==== diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/typerepresentationstrategy.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/typerepresentationstrategy.adoc new file mode 100644 index 000000000..8f011180a --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/programming-model/typerepresentationstrategy.adoc @@ -0,0 +1,56 @@ +[[reference_programming_model_typerepresentationstrategy]] += 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. + +.Example domain model and labels +==== +[source,java] +---- +@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 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. + +== 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. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/reference/setup.adoc b/spring-data-neo4j-docs/src/main/asciidoc/reference/setup.adoc new file mode 100644 index 000000000..c5dd1fe6a --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/reference/setup.adoc @@ -0,0 +1,120 @@ +[[setup]] += 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 http://spring.neo4j.org/examples[Spring Data Neo4j examples]. + +Spring Data Neo4j projects can be built using Maven, Gradle or Ant/Ivy. + +== Dependencies for Spring Data Neo4j + +.Maven dependencies for Spring Data Neo4j +==== +[source,xml] +---- + + org.neo4j.ogm + neo4j-ogm + + + + org.springframework.data + spring-data-neo4j + +---- +==== + + +== 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. + +.Pure Java bean configuration +==== +[source,java] +---- +@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, but is not recommended for production use. 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. +To use the `InProcessServer` in your tests, you will need to add a dependency to the spring-data-neo4j-tests.jar: + +.Maven dependencies for Spring Data Neo4j Test classes +==== +[source,xml] +---- + + org.neo4j.ogm + neo4j-testutils + test-jar + + + + org.springframework.data + spring-data-neo4j-tests + test-jar + +---- +==== + +=== 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. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/springdatagraph.png b/spring-data-neo4j-docs/src/main/asciidoc/springdatagraph.png new file mode 100644 index 000000000..8527a6cf5 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/springdatagraph.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/about-tutorial.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/about-tutorial.adoc new file mode 100644 index 000000000..4196bd175 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/about-tutorial.adoc @@ -0,0 +1,15 @@ +[[tutorial_about-tutorial]] += Introducing our project + +*_Allow me to introduce Cineasts.net_* + +Once upon a time we wanted to build a social movie database. At first there was only the name: Cineasts, the movie enthusiasts who have a burning passion for movies. So we went ahead and bought the domain http://cineasts.net[cineasts.net], and so we were off to a good start. + +We had some ideas about the domain model too. There would obviously be actors playing roles in movies. We also needed someone to rate the movies - enter the cineast. And cineasts, being the social people they are, they wanted to make friends with other fellow cineasts. Imagine instantly finding someone to watch a movie with, or share movie preferences with. Even better, finding new friends and movies based on what you and your friends like. + +When we looked for possible sources of data, IMDB was our first stop. But they're a bit expensive for our taste, charging $15k USD for data access. Fortunately, we found http://themoviedb.org[themoviedb.org] which provides user-generated data for free. They also have liberal terms and conditions, and a nice API for retrieving the data. + +We had many more ideas, but we wanted to get something out there quickly. Here is how we envisioned the final website: + +image::cineasts_main.png[] + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/annotations.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/annotations.adoc new file mode 100644 index 000000000..8c0e4b6f6 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/annotations.adoc @@ -0,0 +1,42 @@ +[[tutorial_annotations]] += Annotating the domain + +*_Decorations_* + +Looking at the Spring Data Neo4j documentation, we found a simple http://spring.neo4j.org/helloworld[Hello World example] and tried to understand it. We also spotted a http://spring.neo4j.org/notes[compact reference card] which helped us a lot. The entity classes were annotated with `@NodeEntity`. That was simple, so we added the annotation to our domain classes too. Entity classes representing relationships were instead annotated with `@RelationshipEntity`. Property fields were taken care of automatically. The only additional field we had to provide for all entities was an id-field to store the node- and relationship-ids. + +.Movie class with annotation +==== +[source,java] +---- +@NodeEntity +class Movie { + @GraphId Long nodeId; + String id; + String title; + int year; + Set cast; +} +---- +==== + +It was time to put our entities to the test. How could we now be assured that an attribute really was persisted to the graph store? We wanted to load the entity and check the attribute. Either we could have a `Neo4jTemplate` injected and use its `findOne(id,type)` method to load the entity. Or use a more versatile `Repository`. The same goes for persisting entities, both `Neo4jTemplate` or the `Repository` could be used. We decided to keep things simple for now. + +So here's what our test ended up looking like: + +.First test case +==== +[source,java] +---- +@Autowired Neo4jTemplate template; + +@Test @Transactional public void persistedMovieShouldBeRetrievableFromGraphDb() { + Movie forrestGump = template.save(new Movie("Forrest Gump", 1994)); + Movie retrievedMovie = template.findOne(forrestGump.getNodeId(), Movie.class); + assertEquals("retrieved movie matches persisted one", forrestGump, retrievedMovie); + assertEquals("retrieved movie title matches", "Forrest Gump", retrievedMovie.getTitle()); +} +---- +==== + +As Neo4j is transactional, we have to provide the transactional boundaries for mutating operations. diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts.png new file mode 100644 index 000000000..e7778456b Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_actor.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_actor.png new file mode 100644 index 000000000..dfa026eb3 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_actor.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_graph.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_graph.png new file mode 100644 index 000000000..514d3b594 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_graph.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_main.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_main.png new file mode 100644 index 000000000..13f3950be Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_main.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_movie.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_movie.png new file mode 100644 index 000000000..7a87e6677 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_movie.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_results.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_results.png new file mode 100644 index 000000000..7e1975da7 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_results.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_user.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_user.png new file mode 100644 index 000000000..69ce800d4 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/cineasts_user.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/conclusion.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/conclusion.adoc new file mode 100644 index 000000000..ebab40370 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/conclusion.adoc @@ -0,0 +1,9 @@ +[[tutorial_conclusion]] += Conclusion + +*_To new frontiers_* + +Pretty neat. We were satisfied with what we got here, with little effort and high performance. Lots of opportunities to expand the social movie database showed up during development. Like adding more social features like tagging, communication streams, location based features (cinemas) and much more. + +But we leave you with that as an exercise to enjoy and explore. Thanks for following the tutorial and make sure to get back to us with suggestions for improvements or reports about unexpected behaviours at the http://spring.neo4j.org/discussions[discussion forums], or the http://spring.neo4j.org/issues[issue tracker]. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/domain.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/domain.adoc new file mode 100644 index 000000000..a038106b9 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/domain.adoc @@ -0,0 +1,55 @@ +[[tutorial_domain]] += The domain model + +*_Setting the stage_* + +We wanted to outline the domain model before diving into library details. We also looked at the data model of the themoviedb.org data to confirm that it matched our expectations. + +image::domain.png[] + +In Java code this looks pretty straightforward: + +.Domain model +==== +[source,java] +---- +class Movie { + String id; + String title; + int year; + Set cast; +} + +class Actor { + String id; + String name; + Set filmography; + Role playedIn(Movie movie, String role) { ... } +} + +class Role { + Movie movie; + Actor actor; + String role; +} + +class User { + String login; + String name; + String password; + Set ratings; + Set friends; + Rating rate(Movie movie, int stars, String comment) { ... } + void befriend(User user) { ... } +} + +class Rating { + User user; + Movie movie; + int stars; + String comment; +} +---- +==== + +Then we wrote some simple tests to show that the basic design of the domain is good enough so far. Just creating a movie, populating it with actors, and allowing users to rate it. diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/domain.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/domain.png new file mode 100644 index 000000000..6765c9c8b Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/domain.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/import.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/import.adoc new file mode 100644 index 000000000..e97a172a4 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/import.adoc @@ -0,0 +1,144 @@ +[[tutorial_import]] += Importing Data + +*_The dusty archives_* + +It was now time to pull the data from http://themoviedb.org[themoviedb.org]. Registering there and getting an API key was simple, as was using the API on the command-line with `curl`. Looking at the JSON returned for movies and people, we decided to enhance our domain model and add some more fields to enrich the UI. + +.JSON movie response +==== +[source,java] +---- +[{"popularity":3, +"translated":true, "adult":false, "language":"en", +"original_name":"[Rec]", "name":"[Rec]", "alternative_name":"[REC]", +"movie_type":"movie", +"id":8329, "imdb_id":"tt1038988", "url":"http://www.themoviedb.org/movie/8329", +"votes":11, "rating":7.2, +"status":"Released", +"tagline":"One Witness. One Camera", +"certification":"R", +"overview":"\"REC\" turns on a young TV reporter and her cameraman who cover the night shift + at the local fire station... +"keywords":["terror", "lebende leichen", "obsession", "camcorder", "firemen", "reality tv ", + "bite", "cinematographer", +"attempt to escape", "virus", "lodger", "live-reportage", "schwerverletzt"], +"released":"2007-08-29", +"runtime":78, +"budget":0, +"revenue":0, +"homepage":"http://www.3l-filmverleih.de/rec", +"trailer":"http://www.youtube.com/watch?v=YQUkX_XowqI", +"genres":[{"type":"genre", +"url":"http://themoviedb.org/genre/horror", +"name":"Horror", +"id":27}], +"studios":[{"url":"http://www.themoviedb.org/company/2270", "name":"Filmax Group", "id":2270}], +"languages_spoken":[{"code":"es", "name":"Spanish", "native_name":"Espa\u00f1ol"}], +"countries":[{"code":"ES", "name":"Spain", "url":"http://www.themoviedb.org/country/es"}], +"posters":[{"image":{"type":"poster", +"size":"original", "height":1000, "width":706, +"url":"http://cf1.imgobject.com/posters/3a0/4cc8df415e73d650240003a0/rec-original.jpg", +"id":"4cc8df415e73d650240003a0"}}, +.... +"cast":[{"name":"Manuela Velasco", +"job":"Actor", "department":"Actors", +"character":"Angela Vidal", +"id":34793, "order":0, "cast_id":1, +"url":"http://www.themoviedb.org/person/34793", +"profile":"http://cf1.imgobject.com/profiles/390/.../manuela-velasco-thumb.jpg"}, +... +{"name":"Gl\u00f2ria Viguer", +"job":"Costume Design", "department":"Costume \u0026 Make-Up", +"character":"", +"id":54531, "order":0, "cast_id":21, +"url":"http://www.themoviedb.org/person/54531", +"profile":""}], +"version":150, "last_modified_at":"2011-02-20 23:16:57"}] +---- +==== + +.JSON actor response +==== +[source,java] +---- +[{"popularity":3, +"name":"Glenn Strange", "known_as":[{"name":"George Glenn Strange"}, {"name":"Glen Strange"}, +{"name":"Glen 'Peewee' Strange"}, {"name":"Peewee Strange"}, {"name":"'Peewee' Strange"}], +"id":30112, +"biography":"", +"known_movies":4, +"birthday":"1899-08-16", "birthplace":"Weed, New Mexico, USA", +"url":"http://www.themoviedb.org/person/30112", +"filmography":[{"name":"Bud Abbott Lou Costello Meet Frankenstein", +"id":3073, +"job":"Actor", "department":"Actors", +"character":"The Frankenstein Monster", +"cast_id":23, +"url":"http://www.themoviedb.org/movie/3073", +"poster":"http://cf1.imgobject.com/posters/4ca/.../bud-abbott-lou-costello-meet-frankenstein-cover.jpg", +"adult":false, "release":"1948-06-15"}, +...], +"profile":[], +"version":19, "last_modified_at":"2011-03-07 13:02:35"}] +---- +==== + +For the import process we created a separate importer using Jackson (a JSON library) to fetch and parse the data, and then some transactional methods in the `MovieDbImportService` to actually import it as movies, roles, and actors. The importer used a simple caching mechanism to keep downloaded actor and movie data on the filesystem, so that we didn't have to overload the remote API. In the code below you can see that we've changed the actor to a person so that we can also accommodate the other folks that participate in movie production. + +.Importing the data +==== +[source,java] +---- +@Transactional +public Movie importMovie(String movieId) { + Movie movie = movieRepository.findById(movieId); + if (movie == null) { // Not found: Create fresh + movie = new Movie(movieId,null); + } + + Map data = loadMovieData(movieId); + if (data.containsKey("not_found")) throw + new RuntimeException("Data for Movie "+movieId+" not found."); + movieDbJsonMapper.mapToMovie(data, movie); + movieRepository.save(movie); + relatePersonsToMovie(movie, data); + return movie; +} + +private void relatePersonsToMovie(Movie movie, Map data) { + Collection cast = (Collection) data.get("cast"); + for (Map entry : cast) { + String id = "" + entry.get("id"); + String jobName = (String) entry.get("job"); + Roles job = movieDbJsonMapper.mapToRole(jobName); + if (job==null) { + continue; + } + switch (job) { + case DIRECTED: + final Director director = doImportPerson(id, new Director(id)); + director.directed(movie); + directorRepository.save(director); + break; + case ACTS_IN: + final Actor actor = doImportPerson(id, new Actor(id)); + actor.playedIn(movie, (String) entry.get("character")); + actorRepository.save(actor); + break; + } + } +} + +public void mapToMovie(Map data, Movie movie) { + movie.setTitle((String) data.get("name")); + movie.setLanguage((String) data.get("language")); + movie.setTagline((String) data.get("tagline")); + movie.setReleaseDate(toDate(data, "released", "yyyy-MM-dd")); +... + movie.setImageUrl(selectImageUrl((List) data.get("posters"), "poster", "mid")); +} +---- +==== + +The last part involved adding a protected URI to the MovieController to allow importing ranges of movies. During testing, it became obvious that the calls to themoviedb.org were a limiting factor. As soon as the data was stored locally, the Neo4j import was a sub-second deal. diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/indexing.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/indexing.adoc new file mode 100644 index 000000000..6983d48c6 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/indexing.adoc @@ -0,0 +1,32 @@ +[[tutorial_indexing]] += Indexing + +*_Do I know you?_* + +There is an @Indexed annotation for fields. We wanted to try this out, and use it to guide the next test. We added @Indexed to the `id` field of the Movie class. This field is intended to represent the external ID that will be used in URIs and will be stable across database imports and updates. That's why we also declare it as unique. This time we went with a simple GraphRepository to retrieve the indexed movie. + +.Exact Indexing for Movie id +==== +[source,java] +---- +@NodeEntity class Movie { + @Indexed(unique=true) String id; + String title; + int year; +} + +@Autowired Neo4jTemplate template; + +@Test @Transactional + public void persistedMovieShouldBeRetrievableFromGraphDb() { + int id = 1; + Movie forrestGump = template.save(new Movie(id, "Forrest Gump", 1994)); + GraphRepository movieRepository = + template.repositoryFor(Movie.class); + Movie retrievedMovie = movieRepository.findByPropertyValue("id", id); + assertEqual("retrieved movie matches persisted one", forrestGump, retrievedMovie); + assertEqual("retrieved movie title matches", "Forrest Gump", retrievedMovie.getTitle()); + } +---- +==== + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/neo4j-server.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/neo4j-server.adoc new file mode 100644 index 000000000..1708fa9bf --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/neo4j-server.adoc @@ -0,0 +1,86 @@ +[[tutorial_server]] += Neo4j Server + +*_Remotely related_* + +Right now our application was running with the embedded mode of Neo4j which was fine and highly performant. In certain environments you don't have the luxury of file-system access for your webapps and have to talk to a remote database service instead. Neo4j can also run as a server. It exposes its operations via a HTTP based REST API. + +We decided to have a look, to be at least knowledgeable about this deployment scenario. We were aware of the difference of local, in-memory calls and higher latency network hops. That would be something we would also take into careful consideration. + +== Getting Neo4j-Server + +Getting the Neo4j-Server was easy, we just went to http://neo4j.org/[neo4j.org] and downloaded the latest version. Starting it on the command-line (or installing it as a service) was a no-brainer as well. + +We copied our store-directory into the `data/graph.db` directory of the server and started it up again. The admin console of Neo4j-Server, called 'web-admin' is pretty. Using JavaScript, it renders the graph visually in a highly configurable way. It also gave us the possibility to issue queries over a console, another handy feature. + +image::webadmin.png[] + +So, how would we get our app connected to this server? It turned out the changes in configuration and setup where minimal. Spring Data Neo4j already came with a module that took care of the remote protocol. We added that maven dependency and changed the graph database used in the Spring Configuration. + +.Maven Dependency +==== +[source,xml] +---- + + org.springframework.data + spring-data-neo4j-rest + 2.1.0.RELEASE + +---- +==== + +.Spring Config +==== +[source,xml] +---- + + + + +---- +==== + +After those two changes we restarted the app, and ... it worked. The transparent handling of the remote API was impressive. We learned that it uses a library called https://github.com/neo4j/java-rest-binding[java-rest-binding] under the hood which is also usable without the Spring Framework. + +Of course we noticed performance implications. Especially after moving the server to a remote machine. It turned out that the server supported remote execution of many operations, allowing us to run the graph traversal and querying inside the server. That means looking at our graph interactions and changing them in a way that switched from the transparent, direct graph access via the entities to a different interaction pattern. + +We looked into the different modes of remotely executed operations and found traversals, Cypher queries and index lookups. Most of them already matched our needs but the Cypher approaches were best suited, because they also handled index operations and allowed to return partial attribute sets and subgraphs. + +So we looked at our use-case (aka page)-based interactions with the graph entities and converted them to Cypher queries on repositories where appropriate, measuring the performance improvements as we went. + +There was also a nice mechanism of mapping Cypher query results to Domain Concepts. You just had to declare and annotate an interface that represents the query results as domain entities and the nodes and relationships returned by Cypher were converted into the appropriate entities. + +.Example of query result mapping +==== +[source,java] +---- +public interface MovieRepository extends GraphRepository { + + @Query("START movie=node:Movie(id={0}) + MATCH movie-[rating?:rating]->(), + movie<-[:ACTS_IN]-actor + RETURN movie, COLLECT(actor), AVG(rating.stars)") + MovieData getMovieData(String movieId); + + @MapResult + public interface MovieData { + @ResultColumn("movie") + Movie getMovie(); + + @ResultColumn("AVG(rating.stars)") + Double getRating(); + + @ResultColumn("COLLECT(actor)") + Iterable getCast(); + } +} +---- +==== + +This allowed us to get all the data needed for rendering a page in a single call to the server, greatly diminishing the chatter between the client and the server. + +== Other approaches + +Another approach to using the Neo4j-Server would be to write a custom server extension using the `SpringPluginInitializer` provided by `spring-data-neo4j-rest`. This extension would use the well known entities and approaches as it runs inside the server atop an embedded graph database. From the extension we would expose custom, domain and use-case oriented REST endpoints that could then be consumed by any kind of webapp, even a pure Javascript based browser app. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/neo4j.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/neo4j.adoc new file mode 100644 index 000000000..c18742d0d --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/neo4j.adoc @@ -0,0 +1,50 @@ +[[tutorial_neo4j]] += Learning Neo4j + +*_Graphs ahead_* + +Now we needed to figure out how to store our chosen domain model in the chosen database. First we read up about graph databases, in particular our chosen one, http://neo4j.org[Neo4j]. The Neo4j data model consists of nodes and relationships, both of which can have key/value-style properties. What does that mean, exactly? Nodes are the graph database name for records, with property keys instead of column names. That's normal enough. Relationships are the special part. In Neo4j, relationships are first-class citizens, meaning they are more than a simple foreign-key reference to another record, relationships carry information. So we can link together nodes into semantically rich networks. This really appealed to us. Then we found that we were also able to http://neo4j.com/docs/milestone/indexing.html[index nodes and relationships] by {key, value} pairs. We also found that we could traverse relationships both imperatively using the core API, and declaratively using a query-like http://neo4j.com/docs/milestone/tutorials-java-embedded-traversal.html[Traversal Description]. Besides those programmatic traversals there was the powerful graph query language called http://neo4j.com/docs/milestone/cypher-query-lang.html[Cypher]. So lots of ways of working with the graph. + +We also learned that Neo4j is fully transactional and therefore upholds http://en.wikipedia.org/wiki/ACID[ACID] guarantees for our data. Durability is actually a good thing and we didn't have to scale to trillions of users and movies yet. This is unusual for NOSQL databases, but easier for us to get our head around than non-transactional eventual consistency. It also made us feel safe, though it also meant that we had to manage transactions. Something to keep in mind later. + +We started out by doing some prototyping with the Neo4j core API to get a feeling for how it works. And also, to see what the domain might look like when it's saved in the graph database. After adding the Maven dependency for Neo4j, we were ready to go. + +.Neo4j Maven dependency +==== +[source,xml] +---- + + org.neo4j + neo4j + 1.8.1 + +---- +==== + +.Neo4j core API (transaction code omitted) +==== +[source,java] +---- +enum RelationshipTypes implements RelationshipType { ACTS_IN }; + +GraphDatabaseService gds = new EmbeddedGraphDatabase("/path/to/store"); +Node forrest=gds.createNode(); +forrest.setProperty("title","Forrest Gump"); +forrest.setProperty("year",1994); +gds.index().forNodes("movies").add(forrest,"id",1); + +Node tom=gds.createNode(); +tom.setProperty("name","Tom Hanks"); + +Relationship role=tom.createRelationshipTo(forrest,ACTS_IN); +role.setProperty("role","Forrest"); + +Node movie=gds.index().forNodes("movies").get("id",1).getSingle(); +assertEquals("Forrest Gump", movie.getProperty("title")); +for (Relationship role : movie.getRelationships(ACTS_IN,INCOMING)) { + Node actor=role.getOtherNode(movie); + assertEquals("Tom Hanks", actor.getProperty("name")); + assertEquals("Forrest", role.getProperty("role")); +} +---- +==== diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/neoclipse.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/neoclipse.png new file mode 100644 index 000000000..7c8e5c559 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/neoclipse.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/recommendations.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/recommendations.adoc new file mode 100644 index 000000000..ba8d461cf --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/recommendations.adoc @@ -0,0 +1,49 @@ +[[tutorial_recommendations]] += Recommendations + +*_Movies! Friends! Bargains!_* + +In the last part of this exercise we wanted to add recommendations to the app. One obvious recommendation was movies that our fiends liked. + +There was this query language called Cypher that looked a bit like SQL but expressed graph matching queries. So we gave it a try, using the `neo4j-shell`, to incrementally expand the query, just by declaring what relationships we wanted to be taken into account and which properties of nodes and relationships to filter and sort on. + +.Cypher based movie recommendation on Repository +==== +[source,java] +---- +interface MovieRepository extends GraphRepository { + @Query(" + match (user)-[:FRIEND]-(friend)-[r:RATED]->(movie) + where id(user) = {0} + return movie + order by avg(r.stars) desc, count(*) desc + limit 10 + ") + Iterable recommendMovies(User me); +} +---- +==== + +But we didn't have enough friends, so it was time to get some suggested. That would be like-minded cineasts that rated movies similarly to us. Again Cypher to the rescue, this time only a bit more complex. Something that became obvious with both queries is that graph queries are always local, so they start from a node, or set of nodes or relationships, and then expand outwards from there. + +.Cypher - Friend Recommendation on Repository +==== +[source,java] +---- +interface UserRepository extends GraphRepository { + @Query(" + start user=node({0}) + match (user)-[r:RATED]->(movie)<-[r2:RATED]-(likeminded), + where id(user) = {0} and r.stars > 3 and r2.stars >= 3 + and not (user)-[:FRIEND]-(likeminded) + with likeminded, count(*) + order by count(*) desc + limit 10 + return likeminded + ") + Iterable suggestFriends(User me); +} +---- +==== + +The controllers simply called these methods, added their results to the model, and the view rendered the recommendations alongside the user's own ratings. diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/relationship.png b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/relationship.png new file mode 100644 index 000000000..f3ae47047 Binary files /dev/null and b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/relationship.png differ diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/relationships.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/relationships.adoc new file mode 100644 index 000000000..611793f37 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/relationships.adoc @@ -0,0 +1,122 @@ +[[tutorial_relationships]] += Relationships + +*_A convincing act_* + +Our application was not very much fun yet, just storing movies and actors. After all, the power is in the relationships between them. Fortunately, Neo4j treats relationships as first class citizens, allowing them to be addressed individually and have properties assigned to them. That allows for representing them as entities if needed. + +== Creating relationships + +Relationships without properties ("anonymous" relationships) don't require any `@RelationshipEntity` classes. "Unfortunately" we had none of those, because our relationships were richer. Therefore we went with the `Role` relationship between `Movie` and `Actor`. It had to be annotated with `@RelationshipEntity` and the `@StartNode` and `@EndNode` had to be marked. So our Role looked like this: + +image::relationship.png[] + +.Role class +==== +[source,java] +---- +@RelationshipEntity +class Role { + @StartNode Actor actor; + @EndNode Movie movie; + String role; +} +---- +==== + +When writing a test for the `Role` we tried to create the relationship entity just by instantiating it with `new` and saving it with the template, but we got an exception saying that it misses the relationship-type. + +We had to add it to the `@RelationshipEntity` as an attribute (or as a @RelationshipType annotated field in the RelationshipEntity). Another way to create instances of relationship-entities is to use the methods provided by the template, like `createRelationshipBetween`. + +.Relating actors to movies +==== +[source,java] +---- +@RelationshipEntity(type="ACTS_IN") +class Role { + @StartNode Actor actor; + @EndNode Movie movie; + String role; +} +class Actor { +... + public Role playedIn(Movie movie, String roleName) { + Role role = new Role(this, movie, roleName); + this.roles.add(role); + return role; + } +} + +Role role = tomHanks.playedIn(forrestGump, "Forrest Gump"); + +// either save the actor +template.save(tomHanks); +// or the role +template.save(role); + +// alternative approach +Role role = template.createRelationshipBetween(actor,movie, + Role.class, "ACTS_IN"); +---- +==== + +Saving just the actor would take care of relationships with the same type between two entities and remove the duplicates. Whereas just saving the role happily creates another relationship with the same type. + +== Accessing related entities + +Now we wanted to find connected entities. We already had fields for the relationships in both classes. It was time to annotate them correctly. The Neo4j relationship type and direction were easy to figure out. The direction even defaulted to outgoing, so we only had to specify it for the movie. If we want to use the same relationship between the two entities we have to make sure to provide a dedicated type, otherwise the field-names would be used resulting in different relationships. + +.@RelatedTo usage +==== +[source,java] +---- +@NodeEntity +class Movie { + @Indexed(unique=true) String id; + String title; + int year; + @RelatedTo(type = "ACTS_IN", direction = Direction.INCOMING) + Set cast; +} + +@NodeEntity +class Actor { + @Indexed(unique=true) int id; + String name; + @RelatedTo(type = "ACTS_IN") + Set movies; + + public Role playedIn(Movie movie, String roleName) { + return new Role(this,movie, roleName); + } +} +---- +==== + +Changes to the collections of related entities are reflected into the graph on saving of the entity. + +We made sure to add some tests for using the relationshhips, so we were assured that the collections worked as advertised. + +== Accessing the relationship entities + +But we still couldn't access the Role relationship entities themselves. It turned out that there was a separate annotation `@RelatedToVia` for accessing the actual relationship entities. And we could declare the field as an `Iterable`, with read-only semantics or on a `Collection` or `Set` field with modifying semantics. So off we went, creating our first real relationship (just kidding). + +To have the collections of relationships being read eagerly during the loading of the Movie we have to annotate it with the @Fetch annotation. Otherwise Spring Data Neo4j refrains from following relationships automatically. The risk of loading the whole graph into memory would be too high. + +.@RelatedToVia usage +==== +[source,java] +---- +@NodeEntity +class Movie { + @Indexed(unique=true) String id; + String title; + int year; + + @Fetch @RelatedToVia(type = "ACTS_IN", direction = Direction.INCOMING) + Iterable roles; +} +---- +==== + +After watching the tests pass, we were confident that the changes to the relationship fields were really stored to the underlying relationships in the graph. We were pretty satisfied with persisting our domain. diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/repository.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/repository.adoc new file mode 100644 index 000000000..2ce18d0a0 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/repository.adoc @@ -0,0 +1,64 @@ +[[tutorial_repository]] += Repositories + +*_Serving a good cause_* + +We wanted to add repositories with domain-specific operations. Interestingly there was support for a very advanced repository infrastructure. You just declare an entity specific repository interface and get all commonly used methods for free without implementing any of boilerplate code. + +So we started by creating a movie-related repository, simply by creating an empty interface. + +.Movie repository +==== +[source,java] +---- +package org.neo4j.cineasts.repository; +public interface MovieRepository extends GraphRepository {} +---- +==== + +Then we enabled repository support in the Spring context configuration by simply adding: + +.Repository context configuration +==== +[source,xml] +---- + +---- +==== + +Besides the existing repository operations (like CRUD, and many standard queries) it was possible to declare custom methods, which we explored later. Those methods' names could be more domain centric and expressive than the generic operations. For simple use-cases like finding by id's this is good enough. So we first let Spring autowire our `MovieController` with the `MovieRepository`. That way we could perform simple persistence operations. + +.Usage of a repository +==== +[source,java] +---- +@Autowired MovieRepository repo; +... + Movie movie = repo.findByPropertyValue("id",movieId); +---- +==== + +We went on exploring the repository infrastructure. A very cool feature was something that we so far only heard about from Grails developers. Deriving queries from method names. Impressive! So we had a more explicit method for the id lookup. + +.Derived movie-repository query method +==== +[source,java] +---- +public interface MovieRepository extends GraphRepository { + Movie getMovieById(String id); +} +---- +==== + +In our wildest dreams we imagined the method names we would come up with, and what kinds of queries those could generate. But some, more complex queries would be cumbersome to read and write. So in those cases it is better to just annotate the finder method. We did this much later, and just wanted to give you a peek into the future. There is much more, you can do with repositories, it is worthwhile to explore. + +.Annotated movie-repository query method +==== +[source,java] +---- +public interface MovieRepository extends GraphRepository { + @Query("start user=node:User({0}) match user-[r:RATED]->movie return movie order by r.stars desc limit 10") + Iterable getTopRatedMovies(User uer); +} +---- +==== diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/running.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/running.adoc new file mode 100644 index 000000000..cc6b7fef1 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/running.adoc @@ -0,0 +1,118 @@ +[[tutorial_running]] += Get it running + +*_Curtains up!_* + +Now we had a pretty complete application. It was time to put it to the test. + +== Populating the database + +Before we opened the gates we needed to add some movie data. So we wrote a small class for populating the database which could be called from our controller. A simple `/populate` endpoint for the controller that called it would be enough for now. + +.Populating the database - Controller +==== +[source,java] +---- +@Service +public class DatabasePopulator { + + @Transactional + public List populateDatabase() { + Actor tomHanks = new Actor("1", "Tom Hanks"); + Movie forrestGump = new Movie("1", "Forrest Gump"); + tomHanks.playedIn(forrestGump,"Forrest"); + template.save(forrestGump); + return asList(forrestGump); + } +} + +@Controller +public class MovieController { + + @Autowired private DatabasePopulator populator; + + @RequestMapping(value = "/populate", method = RequestMethod.POST) + public String populateDatabase(Model model) { + Collection movies = populator.populateDatabase(); + model.addAttribute("movies",movies); + return "/movies/list"; + } +} +---- +==== + +Accessing the URI we could see the list of movies we had added. + +== Inspecting the datastore + +Being the geeks we are, we also wanted to inspect the raw data in the database. Reading the http://docs.neo4j.org/[Neo4j docs], there were a couple of different ways of going about this. + +=== Neoclipse visualization + +First we tried Neoclipse, an Eclipse RCP application that opens an existing graph store and visualizes its content. After getting an exception about concurrent access, we learned that we have to use Neoclipse in read-only mode when our webapp was still running. Good to know. + +image::neoclipse.png[] + +=== The Neo4j Shell + +For console junkies there was also a shell that was able to connect to a running Neo4j instance (if it was started with the `enable_remote_shell=true` parameter), or reads an existing graph store directly. + +.Starting the Neo4j Shell +==== +[source] +---- +bash# neo4j-shell -readonly -path data/graph.db +bash# neo4j-shell -readonly -port 1337 +---- +==== + +The shell was very similar to a standard Bash shell. We were able to `cd` to between the nodes, and `ls` the relationships and properties. There were also more advanced commands for indexing, queries and traversals. + +.Neo4j Shell usage +==== +[source] +---- +neo4j-sh[readonly] (0)$ help +Available commands: index dbinfo ls rm alias set eval mv gsh env rmrel mkrel + trav help pwd paths ... man cd +Use man for info about each command. + +neo4j-sh[readonly] (0)$ index --cd -g User login micha + +neo4j-sh[readonly] (Micha,1)$ ls +*__type__ =[org.neo4j.cineasts.domain.User] +*login =[micha] +*name =[Micha] +*roles =[ROLE_ADMIN,ROLE_USER] +(me) --[FRIEND]-> (Olliver,2) +(me) --[RATED]-> (The Matrix,3) + +neo4j-sh[readonly] (Micha,1)$ ls 2 +*__type__ =[org.neo4j.cineasts.domain.User] +*login =[ollie] +*name =[Olliver] +*roles =[ROLE_USER] +(Olliver,2) <-[FRIEND]-- (me) + +neo4j-sh[readonly] (Micha,1)$ cd 3 + +neo4j-sh[readonly] (The Matrix,3)$ ls +*__type__ =[org.neo4j.cineasts.domain.Movie] +*description =[Neo is a young software engineer and part-time hacker who is singled ...] +*genre =[Action] +*homepage =[http://whatisthematrix.warnerbros.com/] +... +*studio =[Warner Bros. Pictures] +*tagline =[Welcome to the Real World.] +*title =[The Matrix] +*trailer =[http://www.youtube.com/watch?v=UM5yepZ21pI] +*version =[324] +(me) <-[ACTS_IN]-- (Marc Aden,19) +(me) <-[ACTS_IN]-- (David Aston,18) +... +(me) <-[ACTS_IN]-- (Keanu Reeves,6) +(me) <-[DIRECTED]-- (Andy Wachowski,5) +(me) <-[DIRECTED]-- (Lana Wachowski,4) +(me) <-[RATED]-- (Micha,1) +---- +==== diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/security.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/security.adoc new file mode 100644 index 000000000..c793eb99e --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/security.adoc @@ -0,0 +1,179 @@ +[[tutorial_security]] += Adding Security + +*_Protecting assets_* + +To handle an active user in the webapp we had to put it in the session and add login and registration pages. Of course the pages that were only meant for logged-in users had to be secured as well. + +Being Spring users, we naturally used Spring Security for this. We wrote a simple `UserDetailsService` by extending a repository with a custom implementation that takes care of looking up the users and validating their credentials. The config is located in a separate `applicationContext-security.xml`. But first, as always, Maven and `web.xml` setup. + +.Spring Security pom.xml +==== +[source,xml] +---- + + org.springframework.security + spring-security-web + ${spring.version} + + + org.springframework.security + spring-security-config + ${spring.version} + +---- +==== + +.Spring Security web.xml +==== +[source,xml] +---- + + contextConfigLocation + + /WEB-INF/applicationContext-security.xml + /WEB-INF/applicationContext.xml + + + + + org.springframework.web.context.ContextLoaderListener + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + + springSecurityFilterChain + /* + +---- +==== + +.Spring Security applicationContext-security.xml +==== +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + +---- +==== + +.CinceastUserDetailsService interface and UserRepository custom implementation +==== +[source,java] +---- + +public interface CineastsUserDetailsService extends UserDetailsService { + @Override + CineastsUserDetails loadUserByUsername(String login) + throws UsernameNotFoundException, DataAccessException; + + User getUserFromSession(); + + @Transactional + Rating rate(Movie movie, User user, int stars, String comment); + + @Transactional + User register(String login, String name, String password); + + @Transactional + void addFriend(String login, final User userFromSession); +} + +public interface UserRepository extends GraphRepository, + RelationshipOperationsRepository, + CineastsUserDetailsService { + + User findByLogin(String login); +} + +public class UserRepositoryImpl implements CineastsUserDetailsService { + + @Autowired private Neo4jOperations template; + + @Override + public CineastsUserDetails loadUserByUsername(String login) + throws UsernameNotFoundException, DataAccessException { + final User user = findByLogin(login); + if (user==null) throw + new UsernameNotFoundException("Username not found: "+login); + return new CineastsUserDetails(user); + } + + private User findByLogin(String login) { + return template.lookup(User.class,"login",login) + .to(User.class).single(); + } + + @Override + public User getUserFromSession() { + SecurityContext context = SecurityContextHolder.getContext(); + Authentication authentication = context.getAuthentication(); + Object principal = authentication.getPrincipal(); + if (principal instanceof CineastsUserDetails) { + CineastsUserDetails userDetails = (CineastsUserDetails) principal; + return userDetails.getUser(); + } + return null; + } +} + +public class CineastsUserDetails implements UserDetails { + private final User user; + + public CineastsUserDetails(User user) { + this.user = user; + } + + @Override + public Collection getAuthorities() { + User.Roles[] roles = user.getRoles(); + if (roles ==null) return Collections.emptyList(); + return Arrays.asList(roles); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getLogin(); + } + + ... + public User getUser() { + return user; + } +} +---- +==== + +Any logged-in user was now available in the session, and could be used for all the social interactions. The remaining work for this was mainly adding controller methods and JSPs for the views. We used the helper method `getUserFromSession()` in the controllers to access the logged-in user and put it in the model for rendering. Here's what the UI had evolved to: + +image::cineasts_user.png[] diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/setup.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/setup.adoc new file mode 100644 index 000000000..7086d4f4f --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/setup.adoc @@ -0,0 +1,105 @@ +[[tutorial_setup]] += The Spring stack + +Being Spring developers, we naturally choose components from the Spring stack to do all the heavy lifting. After all, we have the concept etched out, so we're already halfway there. + +What database would fit both the complex network of cineasts, movies, actors, roles, ratings, and friends, while also being able to support the recommendation algorithms that we had in mind? We had no idea. + +But hold your horses, there is this new Spring Data project, started in 2010, which brings the convenience of the Spring programming model to NOSQL databases. That should be in line with what we already know, providing us with a quick start. We had a look at the list of projects supporting the different NOSQL databases out there. Only one of them mentioned the kind of social network we were thinking of - Spring Data Neo4j for the Neo4j graph database. Neo4j's slogan of "value in relationships" plus "Enterprise NOSQL" and the accompanying docs looked like what we needed. We decided to give it a try. + +== Required setup + +To set up the project we created a public Github account and began setting up the infrastructure for a Spring web project using Maven as the build system. So we added the dependencies for the Spring Framework libraries, added the `web.xml` for the `DispatcherServlet`, and the `applicationContext.xml` in the webapp directory. + +.Project pom.xml +==== +[source,xml] +---- + + 3.0.7.RELEASE + + + + + org.springframework + + spring-(core,context,aop,aspects,tx,webmvc) + ${spring.version} + + + org.springframework + spring-test + ${spring.version} + test + + +---- +==== + +.Project web.xml +==== +[source,xml] +---- + + org.springframework.web.context.ContextLoaderListener + + + + dispatcherServlet + org.springframework.web.servlet.DispatcherServlet + 1 + + + + dispatcherServlet + / + +---- +==== + +With this setup in place we were ready for the first spike: creating a simple MovieController showing a static view. See the Spring Framework documentation for information on doing this. + +.applicationContext.xml +==== +[source,xml] +---- + + + + + + + + + + + +---- +==== + +.dispatcherServlet-servlet.xml +==== +[source,xml] +---- + + + + + + +---- +==== + +We spun up Tomcat in STS with the App and it worked fine. For completeness we also added Jetty to the maven-config and tested it by invoking `mvn jetty:run` to see if there were any obvious issues with the config. It all seemed to work just fine. diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/social.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/social.adoc new file mode 100644 index 000000000..869a81304 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/social.adoc @@ -0,0 +1,105 @@ +[[tutorial_social]] += Adding social + +*_Movies 2.0_* + +So far, the website had only been a plain old movie database. We now wanted to add a touch of social to it. + +== Users + +So we started out by taking the User class that we'd already coded and made it a full-fledged Spring Data Neo4j entity. We added the ability to create friends and to rate movies. With that we also added a simple UserRepository that was able to look up users by ID. + +The relationships of the user are his friends and the movie-ratings which is implemented with a `Rating` Relationship-Entity. This time we used a different approach (for educational and curiosity purposes) to create the `Rating` relationships. The `createRelationshipBetween` operation of the Neo4jTemplate was our matchmaker of choice. + +.Social entities +==== +[source,java] +---- +@NodeEntity +class User { + @Indexed(unique=true) String login; + String name; + String password; + + @RelatedToVia(type = RATED) + @Fetch Set ratings; + + @RelatedTo(type = "FRIEND", direction=Direction.BOTH) + @Fetch Set friends; + + public Rating rate(Neo4jOperations template, Movie movie, int stars, String comment) { + final Rating rating = template.createRelationshipBetween(this, movie, Rating.class, RATED, false); + rating.rate(stars, comment); + return template.save(rating); + } + + public void addFriend(User user) { + this.friends.add(user); + } +} + +@RelationshipEntity +class Rating { + @StartNode User user; + @EndNode Movie movie; + int stars; + String comment; + public Rating rate(int stars, String comment) { + this.stars = stars; this.comment = comment; + return this; + } +} +---- +==== + +We extended the DatabasePopulator to add some users and ratings to the initial setup. + +.Populate users and ratings +==== +[source,java] +---- +@Transactional +public List populateDatabase() { + Actor tomHanks = new Actor("1", "Tom Hanks"); + Movie forestGump = new Movie("1", "Forrest Gump"); + tomHanks.playedIn(forestGump, "Forrest"); + template.save(tomHanks); + + User me = template.save(new User("micha", "Micha", "password")); + Rating awesome = me.rate(template, forestGump, 5, "Awesome"); + + User ollie = template.save(new User("ollie", "Oliver", "password")); + ollie.rate(template,forestGump, 2, "ok"); + me.addFriend(ollie); + template.save(me); + return asList(forestGump); +} +---- +==== + +== Ratings for movies + +We also put a ratings field into the Movie class to be able to get a movie's ratings, and also a method to average its star rating. + +.Getting the rating of a movie +==== +[source,java] +---- +class Movie { + ... + + @RelatedToVia(type="RATED", direction = Direction.INCOMING) + @Fetch Iterable ratings; + + public int getStars() { + int stars = 0, count = 0; + for (Rating rating : ratings) { + stars += rating.getStars(); count++; + } + return count == 0 ? 0 : stars / count; + } +} +---- +==== + +Fortunately our tests highlighted the division by zero error when calculating the stars for a movie without ratings. The next steps were to add this information to the movie presentation in the UI, and creating a user profile page. But for that to happen, users must first be able to log in. diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/spring-data-neo4j-aj.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/spring-data-neo4j-aj.adoc new file mode 100644 index 000000000..3d975f422 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/spring-data-neo4j-aj.adoc @@ -0,0 +1,101 @@ +[[tutorial_about-spring-data]] += Spring Data Neo4j + +*_Conjuring magic_* + +So far it had all been pure Spring Framework and Neo4j. However, using the Neo4j code in our domain classes polluted them with graph database details. For this application, we wanted to keep the domain classes clean. Spring Data Neo4j promised to do the heavy lifting for us, so we continued investigating it. + +One of the more advanced mapping modes of Spring Data Neo4j depends heavily on AspectJ, see <>. Via the SpringFramework we were already used to Aspects doing the work behind the scenes in lots of places, so we were not afraid. Some parts of our classes would get a bit of new behavior, but it would not be visible in our code. The upside of this is that you get rid of a lot of boilerplate code. + +The first step was to configure Maven: ==== + +.Spring Data Neo4j Maven configuration +[source,xml] +---- + + 1.6.12 + + + + org.springframework.data + spring-data-neo4j-aspects + 2.1.0.RELEASE + + + + org.aspectj + aspectjrt + ${aspectj.version} + +---- + +[source,xml] +---- + + + org.codehaus.mojo + aspectj-maven-plugin + 1.2 + + + org.aspectj + aspectjrt + ${aspectj.version} + + + org.aspectj + aspectjtools + ${aspectj.version} + + + + + + compile + test-compile + + + + + true + + + org.springframework + spring-aspects + + + org.springframework.data + spring-data-neo4j-aspects + + + 1.6 + 1.6 + + + +---- + +==== + +The Spring context configuration was much easier, thanks to a provided namespace: + +==== + +.Spring Data Neo4j context configuration +[source,xml] +---- + + ... + + ... + + +---- + +==== + +We made sure that the AspectJ nature was enabled for this project in our STS 2.7.1 and the Aspect-Path settings of our Project-Properties pointed to the configured aspect-libraries. + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/spring-data-neo4j.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/spring-data-neo4j.adoc new file mode 100644 index 000000000..5874c7578 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/spring-data-neo4j.adoc @@ -0,0 +1,39 @@ +[[tutorial_about-spring-data]] += Spring Data Neo4j + +*_Conjuring magic_* + +So far it had all been pure Spring Framework and Neo4j. However, using the Neo4j code in our domain classes polluted them with graph database details. For this application, we wanted to keep the domain classes clean. Spring Data Neo4j promised to do the heavy lifting for us, so we continued investigating it. + +Spring Data Neo4j comes with two mapping modes. The more powerful one depends heavily on AspectJ, see <>, so we ignored it for the time being. The simple direct POJO-mapping copies the data out of the graph and into our entities. Good enough for a web-application like ours. + +The first step was to configure Maven: + +.Spring Data Neo4j Maven configuration +==== +[source,xml] +---- + + org.springframework.data + spring-data-neo4j + 2.1.0.RELEASE + +---- +==== + +The Spring context configuration was even easier, thanks to a provided namespace: + +.Spring Data Neo4j context configuration +==== +[source,xml] +---- + + ... + + ... + +---- +==== diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/tutorial.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/tutorial.adoc new file mode 100644 index 000000000..0c5764178 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/tutorial.adoc @@ -0,0 +1,28 @@ +include::introduction/rod.adoc[] +include::introduction/emil.adoc[] +[[tutorial]] +== Tutorial + +image::cineasts.png[] + +This tutorial walks through the creation of a complete web application called cineasts.net, built with Spring Data Neo4j. Cineasts are people who love movies, and the site is a gathering place for moviegoers. For cineasts.net we decided to add a social aspect to the rating of movies, allowing friends to share their scores and get recommendations for new friends and movies. + +The tutorial takes the reader through the steps necessary to create the application. It provides the configuration and code examples that are needed to understand what's happening in Spring Data Neo4j. The complete source code for the app is available on http://spring.neo4j.org/cineasts[Github]. + +include::tutorial/about-tutorial.adoc[] +include::tutorial/setup.adoc[] +include::tutorial/domain.adoc[] +include::tutorial/neo4j.adoc[] +include::tutorial/spring-data-neo4j.adoc[] +include::tutorial/annotations.adoc[] +include::tutorial/indexing.adoc[] +include::tutorial/repository.adoc[] +include::tutorial/relationships.adoc[] +include::tutorial/running.adoc[] +include::tutorial/webapp.adoc[] +include::tutorial/social.adoc[] +include::tutorial/security.adoc[] +include::tutorial/user-experience.adoc[] +include::tutorial/import.adoc[] +include::tutorial/recommendations.adoc[] +include::tutorial/conclusion.adoc[] diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/user-experience.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/user-experience.adoc new file mode 100644 index 000000000..b2d2e2acc --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/user-experience.adoc @@ -0,0 +1,15 @@ +[[tutorial_user-experience]] += More UI + +*_Oh the glamour_* + +To create a nice user experience, we wanted to have a nice looking app. Not something that looked like a toddler made it. So we got some user experience people involved and the results were impressive. This sections presents some of the remaining screen shots of Cineasts.net. + +image::cineasts_main.png[] + +image::cineasts_results.png[] + +image::cineasts_movie.png[] + +image::cineasts_actor.png[] + diff --git a/spring-data-neo4j-docs/src/main/asciidoc/tutorial/webapp.adoc b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/webapp.adoc new file mode 100644 index 000000000..4097d6527 --- /dev/null +++ b/spring-data-neo4j-docs/src/main/asciidoc/tutorial/webapp.adoc @@ -0,0 +1,126 @@ +[[tutorial_webapp]] += Web views + +*_Showing off_* + +After having put some data in the graph database, we also wanted to show it to the user. Adding the controller method to show a single movie with its attributes and cast in a JSP was straightforward. It basically just involved using the repository to look the movie up and add it to the model, and then forwarding to the `/movies/show` view and voilá. + +.Controller for showing movies +==== +[source,java] +---- +@RequestMapping(value = "/movies/{movieId}", +method = RequestMethod.GET, headers = "Accept=text/html") +public String singleMovieView(final Model model, @PathVariable String movieId) { + Movie movie = repository.findById(movieId); + model.addAttribute("id", movieId); + if (movie != null) { + model.addAttribute("movie", movie); + model.addAttribute("stars", movie.getStars()); + } + return "/movies/show"; +} +---- +==== + +.Populating the database - JSP /movies/show +==== +[source,xml] +---- +<%@ page session="false" %> + <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> + <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + +

${movie.title} (${stars} Stars)

+ + + +
+ + No Movie with id ${id} found! + +
+---- +==== + +The UI had now evolved to this: + +image::cineasts_movie.png[] + +== Searching + +The next thing was to allow users to search for movies, so we needed some fulltext search capabilities. As the default index provider implementation of Neo4j is based on http://lucene.apache.org/java/docs/index.html[Apache Lucene], we were delighted to see that fulltext indexes were supported out of the box. + +We happily annotated the title field of the Movie class with `@Indexed(type = FULLTEXT)`. Next thing we got an exception telling us that we had to specify a separate index name. So we simply changed it to `@Indexed(type = FULLTEXT, indexName = "search")`. + +With derived finder methods, finding things became easy. By simply declaring a finder-method name that expressed the required properties, it worked without annotations. Cool stuff and you could even tell it that it should return pages of movies, its size and offset specified by a `Pageable` which also contains sort information. Using the `like` operator indicates that fulltext search should be used, instead of an exact search. + +.Searching for movies +==== +[source,java] +---- +public interface MovieRepository ... { + Movie findById(String id); + Page findByTitleLike(String title, Pageable page); + Slice findAll(Pageable page); +} +---- +==== + +== Listing results + +We then used this result in the controller to render a page of movies, driven by a search box. The movie properties and the cast were accessible through the getters in the domain classes. + +.Search controller +==== +[source,java] +---- +@RequestMapping(value = "/movies", +method = RequestMethod.GET, headers = "Accept=text/html") +public String findMovies(Model model, @RequestParam("q") String query) { + Page movies = repository.findByTitleLike(query, new PageRequest(0,20)); + model.addAttribute("movies", movies); + model.addAttribute("query", query); + return "/movies/list"; +} +---- +==== + +.Search Results JSP +==== +[source,xml] +---- +

Movies

+ + + +
+ +
+
+
+
+ +
+
+
+
+ + No movies found for query "${query}". + +
+---- +==== + +The UI now looked like this: + +image::cineasts_results.png[] diff --git a/spring-data-neo4j-examples/sdn-boot/pom.xml b/spring-data-neo4j-examples/sdn-boot/pom.xml index e935ee2c8..ebdf50484 100644 --- a/spring-data-neo4j-examples/sdn-boot/pom.xml +++ b/spring-data-neo4j-examples/sdn-boot/pom.xml @@ -36,7 +36,49 @@ spring-boot-starter-aop - + + + org.neo4j + neo4j + 2.1.7 + + + + org.neo4j.app + neo4j-server + 2.1.7 + + + + org.neo4j.app + neo4j-server + test-jar + 2.1.7 + + + + org.neo4j + neo4j-kernel + 2.1.7 + test-jar + + + + org.neo4j + neo4j-ogm-test + 1.0-SNAPSHOT + + + + org.springframework.data + spring-data-neo4j + 4.0.0.BUILD-SNAPSHOT + test-jar + + + + + com.voodoodyne.jackson.jsog jackson-jsog 1.1 @@ -44,24 +86,26 @@ - org.springframework.data - spring-data-neo4j - 1.0-SNAPSHOT + joda-time + joda-time + 2.3 - + + + org.springframework.data + spring-data-neo4j + 4.0.0.BUILD-SNAPSHOT + + + + org.springframework.boot spring-boot-starter-test test - - joda-time - joda-time - 2.3 - -
diff --git a/spring-data-neo4j-examples/sdn-boot/src/main/java/school/Application.java b/spring-data-neo4j-examples/sdn-boot/src/main/java/school/Application.java index 7e8f5003f..6fe0053dc 100644 --- a/spring-data-neo4j-examples/sdn-boot/src/main/java/school/Application.java +++ b/spring-data-neo4j-examples/sdn-boot/src/main/java/school/Application.java @@ -7,7 +7,11 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.context.annotation.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.env.Environment; import org.springframework.core.env.SimpleCommandLinePropertySource; import org.springframework.data.neo4j.config.Neo4jConfiguration; @@ -17,10 +21,11 @@ import org.springframework.data.neo4j.server.RemoteServer; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.transaction.annotation.EnableTransactionManagement; -import javax.annotation.PostConstruct; import java.io.IOException; import java.util.Arrays; +import javax.annotation.PostConstruct; + // todo: replace these three with @SpringBootApplication @Configuration @@ -41,6 +46,7 @@ public class Application extends Neo4jConfiguration{ public Neo4jServer neo4jServer() { log.info("Initialising server connection"); return new RemoteServer("http://localhost:7474"); + //return new InProcessServer(); } @Override diff --git a/spring-data-neo4j/pom.xml b/spring-data-neo4j/pom.xml index ebb9272f8..ceb87f013 100644 --- a/spring-data-neo4j/pom.xml +++ b/spring-data-neo4j/pom.xml @@ -19,15 +19,10 @@ - org.neo4j.ogm + org.neo4j neo4j-ogm - - org.neo4j.ogm - neo4j-testutils - - org.springframework spring-context @@ -43,6 +38,12 @@ spring-tx + + org.neo4j + neo4j-ogm-test + test + + org.springframework spring-web diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/helloworld/context/HelloWorldContext.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/helloworld/context/HelloWorldContext.java index dc15e1e80..0b95a746a 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/helloworld/context/HelloWorldContext.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/helloworld/context/HelloWorldContext.java @@ -5,7 +5,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; -import org.springframework.data.neo4j.InProcessServer; +import org.springframework.data.neo4j.server.InProcessServer; import org.springframework.data.neo4j.config.Neo4jConfiguration; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; import org.springframework.data.neo4j.server.Neo4jServer; diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/web/context/WebPersistenceContext.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/web/context/WebPersistenceContext.java index bc6196243..3d8b81618 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/web/context/WebPersistenceContext.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/web/context/WebPersistenceContext.java @@ -2,7 +2,7 @@ package org.springframework.data.neo4j.integration.web.context; import org.neo4j.ogm.session.Session; import org.neo4j.ogm.session.SessionFactory; -import org.springframework.data.neo4j.InProcessServer; +import org.springframework.data.neo4j.server.InProcessServer; import org.springframework.context.annotation.*; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; import org.springframework.data.neo4j.config.Neo4jConfiguration; diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/InProcessServer.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/server/InProcessServer.java similarity index 89% rename from spring-data-neo4j/src/test/java/org/springframework/data/neo4j/InProcessServer.java rename to spring-data-neo4j/src/test/java/org/springframework/data/neo4j/server/InProcessServer.java index 2b8ea9ec7..bb1e62bef 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/InProcessServer.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/server/InProcessServer.java @@ -1,8 +1,7 @@ -package org.springframework.data.neo4j; +package org.springframework.data.neo4j.server; import org.neo4j.server.NeoServer; import org.neo4j.server.helpers.CommunityServerBuilder; -import org.springframework.data.neo4j.server.Neo4jServer; import java.io.IOException; diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/TestUtils.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/server/TestUtils.java similarity index 87% rename from spring-data-neo4j/src/test/java/org/springframework/data/neo4j/TestUtils.java rename to spring-data-neo4j/src/test/java/org/springframework/data/neo4j/server/TestUtils.java index 2947880ed..a46184db7 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/TestUtils.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/server/TestUtils.java @@ -1,9 +1,9 @@ -package org.springframework.data.neo4j; +package org.springframework.data.neo4j.server; import java.io.IOException; import java.net.ServerSocket; -public final class TestUtils { +final class TestUtils { public static int getAvailablePort() { try { diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/util/IterableUtilsTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/util/IterableUtilsTest.java index 5e8c4cc0d..d79792ece 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/util/IterableUtilsTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/util/IterableUtilsTest.java @@ -16,8 +16,18 @@ package org.springframework.data.neo4j.util; -import org.junit.After; -import org.junit.Before; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.neo4j.tooling.GlobalGraphOperations.at; +import static org.springframework.data.neo4j.util.IterableUtils.contains; +import static org.springframework.data.neo4j.util.IterableUtils.getFirst; +import static org.springframework.data.neo4j.util.IterableUtils.getFirstOrNull; +import static org.springframework.data.neo4j.util.IterableUtils.getSingle; +import static org.springframework.data.neo4j.util.IterableUtils.getSingleOrNull; + import org.junit.Test; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; @@ -28,14 +38,6 @@ import org.neo4j.test.TestGraphDatabaseFactory; import java.util.Arrays; import java.util.Collections; -import static java.util.Arrays.asList; -import static org.junit.Assert.*; -import static org.neo4j.tooling.GlobalGraphOperations.at; -import static org.springframework.data.neo4j.util.IterableUtils.*; - -/** - * Unit test for {@link com.graphaware.common.util.IterableUtils}. - */ public class IterableUtilsTest { @Test