as the tile above, I have been trying to work with neo4j-ogm and kotlin without success. If I try to persit my data, Neo4j throws an exception, "Class xxxx is not a valid Entity".
package com.asofttz.micros.administrator.users.testmodels
import org.neo4j.ogm.annotation.GeneratedValue
import org.neo4j.ogm.annotation.Id
import org.neo4j.ogm.annotation.NodeEntity
import org.neo4j.ogm.annotation.Relationship
@NodeEntity
class Actor(var name: String = "") {
@Id
@GeneratedValue
open var id: Long? = null
@Relationship(type = "ACTS_IN", direction = "OUTGOING")
open val movies = hashSetOf<Movie>()
fun actsIn(movie: Movie) {
movies.add(movie)
movie.actors.plus(this)
}
}
@NodeEntity
class Movie(var title: String = "", var released: Int = 2000) {
@Id
@GeneratedValue
open var id: Long? = null
@Relationship(type = "ACTS_IN", direction = "INCOMING")
open var actors = setOf<Actor>()
}
Is there a way around? Is there an Alternative to persist data to a Neo4j database with kotlin?
N:B. I am using kotlin version 1.2.60 and Neo4j-OGM v3.2.1
import com.asofttz.micros.administrator.users.testmodels.Actor
import com.asofttz.micros.administrator.users.testmodels.Movie
import org.neo4j.ogm.config.Configuration
import org.neo4j.ogm.session.SessionFactory
import java.util.*
object Neo4j {
val configuration = Configuration.Builder()
.uri("bolt://localhost")
.credentials("neo4j", "password")
.build()
val sessionFactory = SessionFactory(configuration, "test.movies.domain")
fun save() {
val session = sessionFactory.openSession()
val movie = Movie("The Matrix", 1999)
session.save(movie)
val matrix = session.load(Movie::class.java, movie.id)
for (actor in matrix.actors) {
println("Actor: " + actor.name)
}
}
}
build.gradle file looks like this
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: "org.jetbrains.kotlin.plugin.noarg"
repositories {
jcenter()
mavenCentral()
maven { url "http://dl.bintray.com/kotlin/ktor" }
maven { url "https://dl.bintray.com/kotlin/kontlinx" }
}
noArg {
annotation("org.neo4j.ogm.annotation.NodeEntity")
annotation("org.neo4j.ogm.annotation.RelationshipEntity")
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "io.ktor:ktor:$ktor_version"
compile "io.ktor:ktor-server-netty:$ktor_version"
compile project(":asoftlibs:micros:administrator:users:users-jvm")
compile 'org.neo4j:neo4j-ogm-core:3.1.2'
compile 'org.neo4j:neo4j-ogm-bolt-driver:3.1.2'
}
kotlin {
experimental {
coroutines "enable"
}
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
sourceCompatibility = "1.8"
I get the class 'com.asofttz.micros.administrator.users.testmodels.Movie is not a valid entity' further help would be appreciated.
Note: I also attempted in making the movie class open with a no pram contructor, but id ddnt help either. Another attempt was to change the version of neo4j-ogm, so I tested 2.1.5, 3.0.1 and 3.1.2. No success
Edit: Super short answer without explanation is: In your example, you are configuring the wrong package for class scanning. You're open the session with val sessionFactory = SessionFactory(configuration, "test.movies.domain")
but it needs to be val sessionFactory = SessionFactory(configuration, "com.asofttz.micros.administrator.users.testmodels")
judging from the package declaration of your models. But in addition, please see my longer version for some best practices and explanation:
Find the complete and working example as a Gist here: Minimal Kotlin/Gradle Example for Neo4j OGM
Let me walk you through it:
In build.gradle
, define the No-arg compiler plugin as a build script dependency.
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-noarg:1.2.51"
}
}
And than use a noArg
block to define for which classes an no-arguments constructor should be synthesized:
noArg {
annotation("org.neo4j.ogm.annotation.NodeEntity")
annotation("org.neo4j.ogm.annotation.RelationshipEntity")
}
That means: All classes annotated with @NodeEntity
and @RelationshipEntity
should have a synthetic no-args constructor.
I absolutely agree with Jasper that this is the better approach than defaulting all constructor parameters of your domain class, for reference, the kotlin-noarg docs:
The no-arg compiler plugin generates an additional zero-argument constructor for classes with a specific annotation.
The generated constructor is synthetic so it can’t be directly called from Java or Kotlin, but it can be called using reflection.
On to the domain classes: Classes mapped by Neo4j OGM need not to be final. But we don't support final fields and as such, no pure immutable classes. This is just the way things are at the moment.
So here are both domain classes:
@NodeEntity
class Actor(var name: String) {
@Id
@GeneratedValue
var id: Long? = null
@Relationship(type = "ACTS_IN", direction = "OUTGOING")
var movies = mutableSetOf<Movie>()
fun actsIn(movie: Movie) {
movies.add(movie)
movie.actors.add(this)
}
}
@NodeEntity
class Movie(var title: String, var released: Int) {
@Id
@GeneratedValue
var id: Long? = null
@Relationship(type = "ACTS_IN", direction = "INCOMING")
var actors = mutableSetOf<Actor>()
}
Notice that all fields are var
, not val
. You can safely omit the the open
keyword here. Also notice that I did remove the default parameters of the "real" business information (here, title and release-year).
We have to take special care of the sets: I removed the explicit hashSetOf
and instead use the mutableSetOf
. We can than use #add
to mutate the sets itself.
If you prefer a more Kotlin idiomatic way, use setOf
and make use of the fact that our attributes are not final anymore and mutate the fields itself:
@NodeEntity
class Actor(var name: String) {
@Id
@GeneratedValue
var id: Long? = null
@Relationship(type = "ACTS_IN", direction = "OUTGOING")
var movies = setOf<Movie>()
fun actsIn(movie: Movie) {
movies += movie
movie.actors += this
}
}
@NodeEntity
class Movie(var title: String, var released: Int) {
@Id
@GeneratedValue
var id: Long? = null
@Relationship(type = "ACTS_IN", direction = "INCOMING")
var actors = setOf<Actor>()
}
Take note: In your original example, you have a statement like movie.actors.plus(this)
. This does not mutate the set but creates a new one, exactly like the +
operator for sets does.
On a modelling level: I personally would not map the relationship in both directions. This tends to bites you sooner or later, as much as it does in the JPA/ORM world. Map the direction you need for your logic and execute other queries for paths etc. separately.
Please let me know if this helps. I'm closing the GH issue you created now.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With