Introduction to Spring Data Neo4j

1. Overview

This article is an introduction to Spring Data Neo4j, the popular graph database.

Spring Data Neo4j enables POJO based development for the Neo4j Graph Database and uses familiar Spring concepts such as a template classes for core API usage and provides an annotation based programming model.

Also, a lot of developers don’t really know if Neo4j will actually be a good match for their specific needs; here’s a solid overview on Stackoverflow discussing why to use Neo4j and the pros and cons.

2. Maven Dependencies

Let’s start by declaring the Spring Data Neo4j dependencies in the pom.xml. The below mentioned Spring modules are also required for Spring Data Neo4j:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-neo4j</artifactId>
    <version>5.0.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-ogm-test</artifactId>
    <version>3.1.2</version>
    <scope>test</scope>
</dependency>

These dependencies include the required modules for testing as well.

Note that the last dependency is scoped as ‘test’. But also note that, in a real world application development, you’re more likely to have a full Neo4J server running.

If we want to use the embedded server, we also have to add the dependency:

<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-ogm-embedded-driver</artifactId>
    <version>3.1.2</version>
</dependency>

The spring-data-neo4j, neo4j-ogm-test and neo4j-ogm-embedded-driver dependencies are available on Maven Central.

3. Neo4Jj Configuration

The Neo4j configuration is very straight forward and defines the connection setting for the application to connect to the server. Similar to the most of the other spring data modules, this is a spring configuration which can be defined as XML or Java configuration.

In this tutorial, we’ll use Java-based configuration only:

public static final String URL =
  System.getenv("NEO4J_URL") != null ?
  System.getenv("NEO4J_URL") : "http://neo4j:[email protected]:7474";

@Bean
public org.neo4j.ogm.config.Configuration getConfiguration() {
    org.neo4j.ogm.config.Configuration config = new Builder().uri(URL).build();
    return config;
}

@Bean
public SessionFactory getSessionFactory() {
    return new SessionFactory(getConfiguration(),
      "com.baeldung.spring.data.neo4j.domain");
}

@Bean
public Neo4jTransactionManager transactionManager() {
    return new Neo4jTransactionManager(getSessionFactory());
}

As mentioned above, the config is simple and contains only two settings. First – the SessionFactory is referencing the models that we created to represent the data objects. Then, the connection properties with the server endpoints and access credentials.

Neo4j will infer the driver class based on the protocol of the URI, in our case “http”.

Please note that in this example, the connection related properties are configured directly to the server; however in a production application, these should be properly externalized and part of the standard configuration of the project.

4. Neo4j Repositories

Aligning with the Spring Data framework, Neo4j supports the Spring Data repository abstraction behavior. That means accessing the underlying persistent mechanism is abstracted in the inbuilt Neo4jRepository where a project can directly extend it and use the provided operations out-of-the-box.

The repositories are extensible by annotated, named or derived finder methods. Support for Spring Data Neo4j Repositories are also based on Neo4jTemplate, so the underlying functionality is identical.

4.1. Creating the MovieRepository & PersonRepository

We use two repositories in this tutorial for data persistence:

@Repository
public interface MovieRepository extends Neo4jRepository<Movie, Long> {

    Movie findByTitle(@Param("title") String title);

    @Query("MATCH (m:Movie) WHERE m.title =~ ('(?i).*'+{title}+'.*') RETURN m")
    Collection<Movie>
      findByTitleContaining(@Param("title") String title);

    @Query("MATCH (m:Movie)<-[:ACTED_IN]-(a:Person)
      RETURN m.title as movie, collect(a.name) as cast LIMIT {limit}")
    List<Map<String,Object>> graph(@Param("limit") int limit);
}

As you can, the repository contains some custom operations as well as the standard ones inherited from the base class.

Next we have the simpler PersonRepository, which just has the standard operations:

@Repository
public interface PersonRepository extends Neo4jRepository <Person, Long> {
    //
}

You may have already noticed that PersonRepository is just the standard Spring Data interface. This is because, in this simple example, it is almost sufficient to use the inbuilt operations basically as our operation set is related to the Movie entity. However you can always add custom operations here which may wrap single/ multiple inbuilt operations.

4.2. Configuring Neo4jRepositories

As the next step, we have to let Spring know the relevant repository indicating it in the Neo4jConfiguration class created in section 3:

@Configuration
@ComponentScan("com.baeldung.spring.data.neo4j")
@EnableNeo4jRepositories(
  basePackages = "com.baeldung.spring.data.neo4j.repository")
public class MovieDatabaseNeo4jConfiguration {
    //
}

5. The Full Data Model

We already started looking at the data model, so let’s now lay it all out – the full Movie, Role and Person. The Person entity references the Movie entity through the Role relationship.

@NodeEntity
public class Movie {

    @Id @GeneratedValue
    Long id;

    private String title;

    private int released;

    private String tagline;

    @Relationship(type="ACTED_IN", direction = Relationship.INCOMING)

    private List<Role> roles;

    // standard constructor, getters and setters
}

Notice how we’ve annotated Movie with @NodeEntity to indicate that this class is directly mapped to a node in Neo4j.

@JsonIdentityInfo(generator=JSOGGenerator.class)
@NodeEntity
public class Person {

    @Id @GeneratedValue
    Long id;

    private String name;

    private int born;

    @Relationship(type = "ACTED_IN")
    private List<Movie> movies;

    // standard constructor, getters and setters
}

@JsonIdentityInfo(generator=JSOGGenerator.class)
@RelationshipEntity(type = "ACTED_IN")
public class Role {

    @Id @GeneratedValue
    Long id;

    private Collection<String> roles;

    @StartNode
    private Person person;

    @EndNode
    private Movie movie;

    // standard constructor, getters and setters
}

Of course, these last couple of classes are similarly annotated and the -movies reference is linking Person to Movie class by the “ACTED_IN” relationship.

6. Data Access using MovieRepository


==== 6.1. Saving a New Movie Object

Let’s save some data – first, a new Movie, then a Person and of course a Role – including all the relation data we have as well:

Movie italianJob = new Movie();
italianJob.setTitle("The Italian Job");
italianJob.setReleased(1999);
movieRepository.save(italianJob);

Person mark = new Person();
mark.setName("Mark Wahlberg");
personRepository.save(mark);

Role charlie = new Role();
charlie.setMovie(italianJob);
charlie.setPerson(mark);
Collection<String> roleNames = new HashSet();
roleNames.add("Charlie Croker");
charlie.setRoles(roleNames);
List<Role> roles = new ArrayList();
roles.add(charlie);
italianJob.setRoles(roles);
movieRepository.save(italianJob);

6.2. Retrieving an Existing Movie Object by Title

Let’s now verify the inserted movie by retrieving it using the defined title which is a custom operation:

Movie result = movieRepository.findByTitle(title);

6.3. Retrieving an Existing Movie Object by a Part of the Title

It is possible to search to search an existing movie using a part of the title:

Collection<Movie> result = movieRepository.findByTitleContaining("Italian");

6.4. Retrieving All the Movies

All the movies can be retrieve once and can be check for the correct count:

Collection<Movie> result = (Collection<Movie>) movieRepository.findAll();

However there are number of find methods provided with default behavior which is useful for customs requirements and not all are described here.

6.5. Count the Existing Movie Objects

After inserting several movie objects, we can get exiting movie count:

long movieCount = movieRepository.count();

6.6. Deleting an Existing Movie

[source,java,gutter:,true]

movieRepository.delete(movieRepository.findByTitle("The Italian Job"));

After deleting the inserted movie, we can search for the movie object and verify the result is null:

assertNull(movieRepository.findByTitle("The Italian Job"));

6.7. Delete All Inserted Data

It is possible to delete all the elements in the database making the database empty:

movieRepository.deleteAll();

The result of this operation quickly removes all data from a table.

7. Conclusion

In this tutorial, we went through the basics of Spring Data Neo4j using a very simple example.

However Neo4j is capable of catering to very advanced and complex applications having a huge set of relations and networks. And Spring Data Neo4j also offers advanced features to map annotated entity classes to the Neo4j Graph Database.

The implementation of the above code snippets and examples can be found in the GitHub project – this is an Maven based project, so it should be easy to import and run as it is.

Leave a Reply

Your email address will not be published.