OGM in Neo4j

1. What is OGM :

An OGM (Object Graph Mapper) maps nodes and relationships in the graph to objects and references in a domain model. Object instances are mapped to nodes while object references are mapped using relationships, or serialized to properties (e.g. references to a Date). JVM primitives are mapped to node or relationship properties. An OGM abstracts the database and provides a convenient way to persist your domain model in the graph and query it without using low level drivers. It also provides the flexibility to the developer to supply custom queries where the queries generated by Neo4j-OGM are insufficient. (reference)

In previous post, we query by using lower level driver, it is really time-consuming, and we should manually map those statement result to Objects as well.Neo4j-OGM library provides a pure java library. The Neo4j library can persist (annotated) domain objects using Neo4j. It uses Cypher statements to handle those operations in Neo4j.

2. Get start: configuration

Create a Spring Boot project by using spring boot starter Link. Configuration is as follows:

Smiley face

For building an application, your build automation tool needs to be configured to include the Neo4j-OGM dependencies. Neo4j-OGM dependencies consist of neo4j-ogm-core, together with the relevant dependency declarations on the driver you want to use.

porm.xml File:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.neo4j.movie</groupId>
    <artifactId>movie</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>movie</name>
    <description>ogm demo project for  neo4j in Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

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

        <!-- Only add if you're using the Bolt driver -->
        <dependency>
            <groupId>org.neo4j</groupId>
            <artifactId>neo4j-ogm-bolt-driver</artifactId>
            <version>3.1.2</version>
            <scope>runtime</scope>
        </dependency>
        <!--Spring supports neo4j data blinding-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-neo4j</artifactId>
               <version>4.1.1.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Bolt Configuration

The default Bolt port is 7687. Otherwise, a port can be specified with bolt://username:password@localhost:portnumber. Also, the bolt driver allows you to define a connection pool size, which refers to the maximum number of sessions per URL. This property is optional and defaults to 50.

In application.properties file, we set up the username, password and URI.

URI=bolt://user:password@localhost
username="neo4j"
password="root"

Create the Configuration Class. @EnableNeo4jRepositories(basePackages = “com.neo4j.movie.repository”)

package com.neo4j.movie.config;

import org.neo4j.ogm.session.SessionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;


@Configuration
@ComponentScan("com.neo4j.movie")
@EnableTransactionManagement
@EnableNeo4jRepositories(basePackages = "com.neo4j.movie.repository")
public class GraphDBConfiguration{

    @Value("${URI}")
    private String URI;

    @Value("${username}")
    private String username;

    @Value("${password}")
    private String password;

    @Bean
    public org.neo4j.ogm.config.Configuration getConfiguration(){
        return new org.neo4j.ogm.config.Configuration.Builder()
                .uri(URI)
                .credentials(username, password)
                .build();
    }

    @Bean
    public SessionFactory getSessionFactory() {
        return new SessionFactory(getConfiguration(),"com.neo4j.movie.entity");
    }
}

The creation of SessionFactory Should point out the package which stores the Java Bean Objects.

@EnableNeo4jRepositories This is let the Spring Framework knows the repository packages to be scanned. There are three types of drivers, bolt, http and embedded driver.

@Configuration tells spring it is a configuration class

@ComponentScan(“com.neo4j.movie”) tells the spring dependency injection ranges, which means all of classes with annotations from “com.neo4j.movie” packages are recognized as Spring Bean.

@EnableTransactionManagement supports transaction management when interacting with database.

3. Entity Class

Since each node and relationship in the graph should have an identifier, Neo4j-OGM uses this to identify and re-connect the entity to the graph in memory. Identifier may be either a primary id or a native graph id. Native graph id is the id generated by the Neo4j database, while primary id is generated by the user, with @Id and @GeneratedValue annotation. Notice that, Neo4j will reuse deleted node id’s, so it is not recommended to use native graph id, it is recommended users come up with their own unique identifier for their objects.

Hence, we create a abstract entity class and let other relationships and nodes extend that class. So that each object will have a primary id(unique).

package com.neo4j.movie.entity;

import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;

public abstract class Entity {

    @Id
    @GeneratedValue
    private Long id;

    public Long getId() {
        return id;
    }
}

We have two types of nodes which are movies and person.

Java Bean with @NodeEntity will be recognized as nodes in the graph. @NodeEntity has a property called label to specify the label assigned to the classes. if not specified, it will default to the simple class name of the entity.At the same time each parent class also contributes a label to the entity (with the exception of java.lang.Object).Person has two fields, which are name and born.

@NodeEntity
public class Person extends Entity{

    private String name;
    private Integer born;

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getBorn() {
        return born;
    }

    public void setBorn(Integer born) {
        this.born = born;
    }
}
@NodeEntity
public class Movie extends Entity {

    private String title;
    private int released;
    private String tagline;

    public Movie() {
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getReleased() {
        return released;
    }

    public void setReleased(int released) {
        this.released = released;
    }

    public String getTagline() {
        return tagline;
    }

    public void setTagline(String tagline) {
        this.tagline = tagline;
    }
}

RelationShips for person: person may active in the movie. @RelationShip annotation allows user to the type of relationship and direction as well as the direction. By default, the direction is assumed to be OUTGOING. Use Relationship.INCOMING and Relationship.OutCOMING to specify the direction.

NodeEntity
public class Person extends Entity{

    private String name;
    private Integer born;

    @Relationship(type = "ACTED_IN",direction = Relationship.OUTGOING)
    private Set<Movie> movies = new HashSet<>();

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getBorn() {
        return born;
    }

    public void setBorn(Integer born) {
        this.born = born;
    }

    public Set<Movie> getMovies() {
        return movies;
    }

    public void setMovies(Set<Movie> movies) {
        this.movies = movies;
    }
}
package com.neo4j.movie.entity;

import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;

import java.util.Set;

@NodeEntity
public class Movie extends Entity {

    private String title;
    private int released;
    private String tagline;

    @Relationship(type = "ACTED_IN", direction = Relationship.INCOMING)
    private Set<Person> actPerson;

    public Movie() {
    }

    public String getTitle() {
        return title;
    }

    public Set<Person> getActPerson() {
        return actPerson;
    }

    public void setActPerson(Set<Person> actPerson) {
        this.actPerson = actPerson;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getReleased() {
        return released;
    }

    public void setReleased(int released) {
        this.released = released;
    }

    public String getTagline() {
        return tagline;
    }

    public void setTagline(String tagline) {
        this.tagline = tagline;
    }
}

Relationship Entities:

Some RelationShips also have properties and property value. A relationship entity must be annotated with @RelationshipEntity and also the type of relationship. @StartNode and @EndNode will indicate to Neo4j-OGM the start and end node of this relationship.

@RelationshipEntity(type = "ACTED_IN")
public class Role extends Entity {

    @StartNode
    private Person person;

    @EndNode
    private Movie movie;


    private List<String> roles = new ArrayList<>();

    public Role() {
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public Movie getMovie() {
        return movie;
    }

    public void setMovie(Movie movie) {
        this.movie = movie;
    }

    public List<String> getRoles() {
        return roles;
    }

    public void setRoles(List<String> roles) {
        this.roles = roles;
    }
}

4. Repository And Test.

The repository interface is really like how JPA works.

In previous post, we use pure Java API to find Actor By Movie Title. Here ,we do the same thing.

PersonRepository

package com.neo4j.movie.repository;

import com.neo4j.movie.entity.Person;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.repository.query.Param;

import java.util.Set;


public interface PersonRepository extends Neo4jRepository<Person, Long>{

    Person findByName(@Param("name") String name);


    @Query("MATCH (movie: Movie {title:$title})<-[:ACTED_IN]-(person:Person) RETURN person")
    Set<Person> findActorByMovieTitle(@Param("title") String title);

}

Unit Test

package com.neo4j.movie.repository;

import com.neo4j.movie.entity.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Set;

import static org.junit.Assert.*;


@RunWith(SpringRunner.class)
@SpringBootTest
public class PersonRepositoryTest {
    @Autowired
    private PersonRepository personRepository;

    @Test
    public void findActorByMovieTitle(){
        Set<Person> personSet = personRepository.findActorByMovieTitle("The Matrix");

        for(Person person:personSet){
            System.out.println(person.getName());
        }

        assertEquals(personSet.size(),5);
    }

}

The outPut is as follows:

Emil Eifrem
Hugo Weaving
Laurence Fishburne
Carrie-Anne Moss
Keanu Reeves
Process finished with exit code 0

The results are the same compared with my previous post.

I will play around with more examples, and go through the documentation spring-data-neo4j.

To be continued…


Author: Liang Tan
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Liang Tan !
  TOC