Introduction

Neo4j a Graph database that fits nicely in a Grails application.

The goal of GORM for Neo4j is to provide a as-complete-as-possible GORM implementation that maps domain classes and instances to the Neo4j nodespace. The following features are supported:

  • Marshalling from Neo4j Nodes to Groovy types and back again

  • Support for GORM dynamic finders, criteria and named queries

  • Session-managed transactions

  • access to Neo4j’s traversal capabilities

  • Access the Neo4j graph database in all flavours (Embedded, REST and HA)

  • Neo4j autoindexing

Compatibility with GORM for Hibernate

This implementation tries to be as compatible as possible with GORM for Hibernate. In general you can refer to the GORM documentation and the "Domain Classes" section of the reference guide (see the left nav) for usage information.

The following key features are supported by GORM for Neo4j:

  • Simple persistence methods (save,delete etc)

  • Dynamic finders

  • Criteria queries

  • Named queries

  • Inheritance

  • Embedded types

  • Query by example

  • Many-to-many associations (these can be modelled with a mapping class)

However, some features are not supported:

  • HQL queries (however Cypher Queries are)

  • Composite primary keys

  • Any direct interaction with the Hibernate API

  • Custom Hibernate user types

There may be other limitations not mentioned here so in general it shouldn’t be expected that an application based on GORM for Hibernate will "just work" without some tweaking involved. Having said that, the large majority of common GORM functionality is supported.

Release Notes

The following new features are available in this release.

  • Support for Neo4j Neo4j 2.3.x or above

  • Ability to query using Cypher with the default GORM methods (find, findAll)

  • Robust Spring Transaction Management

  • Support for Lazy & Eager Loading using OPTIONAL MATCH

  • Improved Performance

  • Dirty Checking Implementation

If you are using an older version of the plugin, and looking to upgrade the following changes may impact you:

  • Neo4j JDBC is no longer used and the corresponding CypherEngine interface was removed

  • Dynamic associations are disabled by default, you can re-enable them in your entity mapping

Getting Started

To get started with GORM for Neo4j you need to install the plugin into a Grails application.

For Grails 3.x you need to edit your build.gradle file and add the plugin as a dependency:

dependencies {
        compile 'org.grails.plugins:neo4j:VERSION'
}

To do so for Grails 2.x edit the grails-app/conf/BuildConfig.groovy file and add the following plugin definition, where VERSION is the version of the plugin you plan to use:

plugins {
    compile ':neo4j:VERSION'
}
GORM for Neo4j requires Grails 2.5.x or above and Neo4j 2.3.x or above

By default Neo4j will used as embedded database inside the JVM, the default directory for the Neo4j datastore is data/neo4j.

Using Neo4j Standalone

If you plan to use Neo4j as your primary datastore then you need to remove the Hibernate plugin by editing your BuildConfig or build.gradle (dependending on the version of Grails) and removing the Hibernate plugin definition

With this done all domain classes in grails-app/domain will be persisted via Neo4j and not Hibernate. You can create a domain class by running the regular create-domain-class command:

grails create-domain-class Person

The Person domain class will automatically be a persistent entity that can be stored in Neo4j.

Combining Neo4j And Hibernate

If you have both the Hibernate and Neo4j plugins installed then by default all classes in the grails-app/domain directory will be persisted by Hibernate and not Neo4j. If you want to persist a particular domain class with Neo4j then you must use the mapWith property in the domain class:

static mapWith = "neo4j"

Advanced Configuration

As mentioned the GORM for Neo4j plugin will configure all the defaults for you, but if you wish to customize those defaults you can do so in the your grails-app/conf/DataSource.groovy file or in the grails-app/conf/application.groovy file for Grails 3.x and above:

grails {
    neo4j {
        type = "embedded"
        location = "/var/neo4j"
    }
}

The type provides currently the following choices:

type = "embedded"

Runs Neo4j in embedded mode, Neo4j and Grails use the same JVM. No seperate setup outside the Grails application is required. location specifies the directory where Neo4j stores its data.

Example:

grails {
    neo4j {
        type = "embedded"
        location = "/var/neo4j"
    }
}
If your configuration is empty, embedded is used as default.

type = "rest"

Uses a org.grails.datastore.gorm.neo4j.rest.GrailsCypherRestGraphDatabase instance to connect to a Neo4j database. See http://neo4j.com/docs/stable/server-installation.html for how to setup a Neo4j server.

location specifies the URL of he Neo4j REST server. When using the Heroku Neo4j addon, omit the location. In this case location will default to env.NEO4J_URL that is provided by Heroku.

Example:

grails {
    neo4j {
        type = "rest"
        location = "http://localhost:7474/db/data/"
    }
}

Additionally you must add the following dependencies to your application’s build.gradle or grails-app/conf/BuildConfig.groovy file:

compile 'org.springframework.data:spring-data-neo4j:3.4.0.RELEASE'
compile 'org.springframework.data:spring-data-neo4j-rest:3.4.0.RELEASE'
    exclude group:'org.neo4j.test', module:'neo4j-harness'
}

GORM for Neo4j uses the REST implementation from the Spring Data Neo4j REST project.

type = "ha"

Uses a Neo4j HA setup, for details see http://neo4j.com/docs/stable/ha.html. In this case params must at least contain

For Neo4j HA either a commercial license is required, or you could use AGPL.

Example:

grails {
    neo4j {
        type = "ha"
        location = "/var/neo4j"
        // see http://neo4j.com/docs/stable/ha-configuration.html
        options = [
                'ha.server_id': 1,
                'ha.coordinators': 'localhost:2181,localhost:2182,localhost:2183'
        ]
    }
}

Additionally you must add another dependency to your application’s build.gradle or grails-app/conf/BuildConfig.groovy:

compile 'org.neo4j:neo4j-ha:$neo4jVersion'

type = "impermanent"

Uses ImpermanentGraphDatabase which is good for testing and early stage development.

This option required a dependency to artifact [group: "org.neo4j", name:"neo4j-kernel", version:neo4jVersion, classifier:'tests'] in build.gradle or BuildConfig.groovy.

compile group: "org.neo4j", name: "neo4j-kernel", version: neo4jVersion, classifier: 'tests'
ImpermanentGraphDatabase is intended to be used for testing.

custom graph database

If you use a custom implementation of GraphDatabaseService, you can use

grails {
    neo4j {
        type = "my.fancy.custom.GraphDatabaseServiceImplementation"
        location = "/var/neo4j"
        options = [ :]
    }
}

Using GORM in Spring Boot

To use GORM for Neo4j in Spring Boot add the necessary dependencies to your Boot application:

compile("org.grails:gorm-neo4j-spring-boot:VERSION")

Ensure your Boot Application class is annotated with ComponentScan, example:

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.context.annotation.*

@Configuration
@EnableAutoConfiguration
@ComponentScan
class Application {
    static void main(String[] args) {
        SpringApplication.run Application, args
    }
}
Using ComponentScan without a value results in Boot scanning for classes in the same package or any package nested within the Application class package. If your GORM entities are in a different package specify the package name as the value of the ComponentScan annotation.

Finally create your GORM entities and ensure they are annotated with grails.persistence.Entity:

import grails.persistence.*

@Entity
class Person {
    String firstName
    String lastName
}

GORM for Neo4j without Grails

If you wish to use GORM for Neo4j outside of a Grails application you should declare the necessary dependencies, for example in Gradle:

compile "org.grails:grails-datastore-gorm-neo4j:VERSION"

Then annotate your entities with the grails.gorm.annotation.Entity annotation:

@Entity
class Person {
    String name
}

Then you need to place the bootstrap logic somewhere in your application which uses Neo4jDataStoreSpringInitializer:

def initializer = new Neo4jDataStoreSpringInitializer(Person)
def applicationContext = initializer.configure()

println Person.count()

For configuration you can either pass a map or an instance of the org.springframework.core.env.PropertyResolver interface:

def initializer = new Neo4jDataStoreSpringInitializer(['grails.neo4j.location':'http://myserver'], Person)
def applicationContext = initializer.configure()

println Person.count()

If you are using Spring with an existing ApplicationContext you can instead call configureForBeanDefinitionRegistry prior to refreshing the context. You can pass the Spring Environment object to the constructor for configuration:

ApplicationContext myApplicationContext = ...
def initializer = new Neo4jDataStoreSpringInitializer(myApplicationContext.getEnvironment(), Person)
initializer.configureForBeanDefinitionRegistry(myApplicationContext)

println Person.count()

Mapping domain classes to Neo4j node space

GORM for Neo4j will map each Grails domain instance to a Node in the node space. For example given the following domain class:

class Pet {
    String name
}

When an instance of Pet is saved:

def p = new Pet(name:"Dino")
p.save(flush:true)

Then the following Cypher CREATE is issued:

CREATE (n2:Pet {props})

The properties of the class are converted to native Neo4j types and set as the props parameter to the query.

If you want to see what queries GORM for Neo4j generates, enabling logging for the org.grails.datastore.gorm.neo4j package

Neo4j ID generators

GORM by default uses a custom identity generator based on the Snowflake algorithm and stores the generated identifier in a property of each Neo4j node called __id__.

This has the advantage of enabling GORM to batch insert operations into a single CREATE cypher statement, hence why it is the default.

However, if you prefer to use native Neo4j node identifiers then you can do so by changing the generator used in the mapping:

class Club {

    ...
    static mapping = {
        id generator:'native'
    }

}

This will instead use the Neo4j node identifier as the object identifier with the downside being that GORM has to execute individual CREATE statements for each new object saved, similar to the way GORM for Hibernate has to perform a SQL INSERT if the id generation strategy is an auto-increment column.

This disadvantage can be worked around by using the saveAll method to save multiple domain classes at once:

Club.saveAll([
    new Club(name:"Manchester United"),
    new Club(name:"Arsenal")
])

If you wish to globally change id generation then you can do so in grails-app/conf/application.groovy (Config.groovy in Grails 2.x):

grails.neo4j.default.mapping = {
    id generator:'native'
}

In addition, if you wish to use a custom identity generation strategy you can do so by specifying a class name that implements the IdGenerator interface:

grails.neo4j.default.mapping = {
    id generator:'com.foo.MyIdGenerator'
}

Understanding Association Mapping

GORM for Neo4j will create Neo4j relationships between nodes for you based on the relationship ownership rules defined in your GORM mapping. For example the following mapping:

class Owner {
    String name
    static hasMany = [pets:Pet]
}
class Pet {
    String name
    static belongsTo = [owner:Owner]
}

Will generate a Cypher relationship creation query as follows:

MATCH (from:Owner {__id__:{start}}), (to:Pet) WHERE to.__id__ IN {end} CREATE (from)-[r:PETS]->(to)

As you can see from the query the relationship is defined as (from)-[r:PETS]->(to), with the direction of the association defined by who "owns" the association. Since Pet defines a belongTo association to Owner, this means that Owner owns the association and the relationship is from Owner to Pet.

For more information on defining relationships with GORM, see the corresponding guide in the GORM documentation.

Customizing the Label Strategy

The default strategy for defining labels is to use the class name, however the strategy to define labels for a given node is completely configurable. For example you can use static mapping to define you labels:

class Person {
    static mapping = {
        labels "Person", "People"
    }
}

You can also define labels dynamically. For example:

class Person {
    static mapping = {
        labels { GraphPersistentEntity pe -> "`${pe.javaClass.name}`" }
    }
}

Or mix static and dynamic labels:

static mapping = {
    labels "People", { GraphPersistentEntity pe -> "`${pe.javaClass.name}`" }
}

At a small read/write performance cost you can define dynamic labels based on an instance:

static mapping = {
    labels { GraphPersistentEntity pe, instance ->  // 2 arguments: instance dependent label
        "`${instance.profession}`"
    }
}

Dynamic Properties and Associations

Neo4j is a schemaless database. This means that, unlike SQL where you can only have a fixed number of rows and columns, nodes can have unlimited properties.

Most existing object mapping tools in statically typed languages don't allow you to harness this power, but GORM for Neo4j allows you to define both statically defined properties (ie the properties of the domain class) and domain properties.
For example, take the following domain class:
[source,groovy]
class Plant {
    String name
 }
You can set both the statically defined `name` property, but also any arbitrary number of dynamic properties using the subscript operator in Groovy:
[source,groovy]
def p = new Plant(name:"Carrot")
  p['goesInPatch'] = true
  p.save(flush:true)
Any simple properties can be included, however if you wish to have dynamic associations you can as well by modifying the mapping:
[source,groovy]
class Plant {
    String name
    static mapping = {
        dynamicAssociations true
    }
 }
With this in place you can define dynamic associations:
[source,groovy]
def p = new Plant(name:"Carrot")
  p['related'] = [ new Plant(name:"Potato").save() ]
  p.save(flush:true)
The downside of dynamic associations is that GORM has to issue a separate query for each loaded instance when retrieving entities. If you have a small amount of data this is not a problem, but for larger data sets it is not recommended.

Querying with GORM for Neo4j

GORM for Neo4j supports all the different querying methods provided by GORM including:

However, HQL queries are not supported, instead you can use Cypher directly if you so choose.

If you want to see what queries GORM for Neo4j generates, enabling logging for the org.grails.datastore.gorm.neo4j package

Understanding Lazy Loading

When retrieving a GORM entity and its associations by default single-ended associations will only retrieve the association id, whilst associations to many objects will not retrieve the association at all until it is accessed. This is called lazy loading.

For example given the following domain model:

class League {
    String name
    static hasMany = [clubs:Club]
}
class Club {
    String name
    static belongsTo = [league:League]
    static hasMany = [teams: Team ]
}
class Team  {
    String name
    static belongsTo = [club:Club]
}

When you retrieve the Club by name:

def club = Club.findByName("Manchester United")

You will get the following Cypher query:

MATCH (n:Club) WHERE ( n.__id__={1} )
OPTIONAL MATCH(n)-[:LEAGUE]->(leagueNode)
RETURN n as data, collect(DISTINCT leagueNode.__id__) as leagueIds

As you can see the teams association is not loaded in the query, and only the ID of the league association is retrieved. If you then iterate over the teams you will get a second query to obtain the teams:

for(team in club.teams) {
    println team.name
}

The query generated will be:

MATCH (from:Club)<-[:CLUB]-(to:Team) WHERE from.__id__ = {id} RETURN to as data

If you wish to avoid this secondary query to retrieve the data you can do so using an eager query:

// using a dynamic finder
def club = Club.findByName("Manchester United", [fetch:[teams:'join']])

// using a where queries
def query = Club.where { name == "Manchester United" }
                .join('teams')
def club = query.find()

// using criteria
def query = Club.createCriteria()
def club = query.get {
    eq 'name', "Manchester United"
    join 'teams'
}

This will instead generate the following query:

MATCH (n:Club) WHERE ( n.name={1} )
OPTIONAL MATCH(n)<-[:CLUB]-(teamsNode)
OPTIONAL MATCH(n)-[:LEAGUE]->(leagueNode)
RETURN n as data, collect(DISTINCT leagueNode.__id__) as leagueIds, collect(DISTINCT teamsNode) as teamsNodes

As you can see the associated team nodes are loaded by the query. If you prefer this to happen for every query, then this can also be configured in the mapping:

class Club {
    ...

    static mapping = {
       teams fetch:"eager"
    }
}

You can also configure the collection ids to be eagerly loaded, but the instances themselves to be lazy loaded via proxies:

class Club {
    ...

    static mapping = {
       teams fetch:"eager", lazy:true
    }
}

Querying with Cypher

To query with raw Cypher queries you can use the built in find and findAll methods:

[source,groovy]
def club = Club.find("MATCH n where n.name = {1} RETURN n", 'FC Bayern Muenchen')
 def clubs = Club.findAll("MATCH n where n.name = {1} RETURN n", 'FC Bayern Muenchen')
Note that the first returned item should be the node itself. To execute cypher queries and work with the raw results use `cypherStatic`:
[source,groovy]
Result result = Club.cypherStatic("MATCH n where n.name = {1} RETURN n", 'FC Bayern Muenchen')
When working with raw results, you can convert any `org.neo4j.graphdb.Node` into a domain instance using the `as` keyword:
[source,groovy]
Node myNode = ...
 Club club = myNode as Club
You can also convert any `org.neo4j.graphdb.Result` instance to a list of domain classes:
[source,groovy]
Result result = ...
  List<Club> clubs = result.toList(Club)

Defining the Query Index

To define which properties of your domain class should be indexed for querying you can do so in the mapping:

class Club {
    String name

    ...
    static mapping = {
        name index:true
    }
}

On startup GORM will use Cypher to create indexes as follows:

CREATE INDEX ON :Club(name)

To define a unique index use unique instead:

class Club {
    String name

    ...
    static mapping = {
        name unique:true
    }
}

Enhancements to Neo4j core API

Neo4j Grails contains some enhancements to the Neo4j core API.

Setting properties on nodes/relationships

Assigning an arbitrary property onto a Neo4j node or relationship can be simply done by using Groovy’s property mechanism:

def node = graphDatabaseService.createNode()
node.myProperty = myValue

The same applies for getting properties:

def node = ...
def value = node.myProperty

Reference

Additional Gorm Methods

cypher

cypher
Purpose

Execute a cypher query.

Example
setup:
def person = new Person(firstName: "Bob", lastName: "Builder")
def petType = new PetType(name: "snake")
def pet = new Pet(name: "Fred", type: petType, owner: person)
person.addToPets(pet)
person.save(flush: true)
session.clear()

when:
def result = person.cypher("start n=node({this}) match n-[:pets]->m return m")

then:
result.iterator().size() == 1
Description

cypher is invoked on any domain instance and returns a ExecutionResult The parameters passed are: * cypher query string. The query string might use a implizit this parameter pointing to the instance’s node * a optional map of cypher parameters

cypherStatic

cypherStatic
Purpose

Execute a cypher query.

Example
setup:
new Person(lastName:'person1').save()
new Person(lastName:'person2').save()
session.flush()
session.clear()

when:
def result = Person.cypherStatic("start n=node({this}) match n-[:INSTANCE]->m where m.lastName='person1' return m")

then:
result.iterator().size()==1
Description

cypherStatic is invoked on any domain class and returns a ExecutionResult The parameters passed are: * cypher query string. The query string might use a implicit this parameter pointing to the domain class’s (aka subreference) node * a optional map of cypher parameters

[[ref-additional-gorm-methods-schemaless attributes]] ==== schemaless attributes

schemaless attributes
Purpose

For domain classes mapped by Neo4j you can put and get arbitrary attributes on a instances by using the dot operator or map semantics on the domain instance.

Setting arbitrary attribute in only allowed when the domain instance is persisted (aka save has been called)!
Example

A simple domain class:

class Person implements Serializable {
    String firstName
    String lastName
    Integer age = 0
}
using map semantics
when:
def person = new Person(lastName:'person1').save()
person['notDeclaredProperty'] = 'someValue'   // n.b. the 'dot' notation is not valid for undeclared properties
person['emptyArray'] = []
person['someIntArray'] = [1,2,3]
person['someStringArray'] = ['a', 'b', 'c']
person['someDoubleArray'] = [0.9, 1.0, 1.1]
session.flush()
session.clear()
person = Person.get(person.id)

then:
person['notDeclaredProperty'] == 'someValue'
person['lastName'] == 'person1'  // declared properties are also available via map semantics
person['someIntArray'] == [1,2,3]
person['someStringArray'] == ['a', 'b', 'c']
person['someDoubleArray'] == [0.9, 1.0, 1.1]
using dot operator
when:
def person = new Person(lastName:'person1').save(flush:true)
session.clear()
person = Person.load(person.id)
person.notDeclaredProperty = 'someValue'   // n.b. the 'dot' notation is not valid for undeclared properties
person.emptyArray = []
person.someIntArray = [1,2,3]
person.someStringArray = ['a', 'b', 'c']
person.someDoubleArray= [0.9, 1.0, 1.1]
session.flush()
session.clear()
person = Person.get(person.id)

then:
person.notDeclaredProperty == 'someValue'
person.lastName == 'person1'  // declared properties are also available via map semantics
person.someIntArray == [1,2,3]
person.someStringArray == ['a', 'b', 'c']
person.emptyArray == []
person.someDoubleArray == [0.9, 1.0, 1.1]
Description

The non declared attribtes are stored a regular properties on the domain instance’s node. The values of the schemaless attributes must be a valid type for Neo4j property (String, primitives and arrays of the former two).

Beans

graphDatabaseService

graphDatabaseService
Purpose

A Spring bean that provides access to the lower level Neo4j GraphDatabaseService instance.

Examples
def graphDatabaseService

def foo = {
    def graphDatabaseService
    def myAction = {
        def node = graphDatabaseService.createNode() // create
        ... do something with node ...s
    }
}
Description

See the Neo4j Api doc docs for how to use the graphDatabaseService bean.

Configuration

tbd