Kotlin with JPA: default constructor hell
Solution 1:
As of Kotlin 1.0.6, the kotlin-noarg
compiler plugin generates synthetic default construtors for classes that have been annotated with selected annotations.
If you use gradle, applying the kotlin-jpa
plugin is enough to generate default constructors for classes annotated with @Entity
:
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
}
}
apply plugin: "kotlin-jpa"
For Maven:
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration>
<compilerPlugins>
<plugin>jpa</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
Solution 2:
just provide default values for all arguments, Kotlin will make default constructor for you.
@Entity
data class Person(val name: String="", val age: Int=0)
see the NOTE
box below the following section:
https://kotlinlang.org/docs/reference/classes.html#secondary-constructors
Solution 3:
@D3xter has a good answer for one model, the other is a newer feature in Kotlin called lateinit
:
class Entity() {
constructor(name: String, age: Date): this() {
this.name = name
this.birthdate = age
}
lateinit var name: String
lateinit var birthdate: Date
}
You would use this when you are sure something will fill in the values at construction time or very soon after (and before first use of the instance).
You will note I changed age
to birthdate
because you cannot use primitive values with lateinit
and they also for the moment must be var
(restriction might be released in the future).
So not a perfect answer for immutability, same problem as the other answer in that regard. The solution for that is plugins to libraries that can handle understanding the Kotlin constructor and mapping properties to constructor parameters, instead of requiring a default constructor. The Kotlin module for Jackson does this, so it is clearly possible.
See also: https://stackoverflow.com/a/34624907/3679676 for exploration of similar options.
Solution 4:
Adding the JPA plugin in gradle worked for me:
plugins {
id("org.springframework.boot") version "2.3.4.RELEASE"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
kotlin("jvm") version "1.3.72"
kotlin("plugin.spring") version "1.3.72"
kotlin("plugin.jpa") version "1.3.72"
}
Solution 5:
@Entity data class Person(/*@Id @GeneratedValue var id: Long? = null,*/
var name: String? = null,
var age: Int? = null)
Initial values are requires if you want reuse constructor for different fields, kotlin doesn't allowed nulls. So whenever you planning omit field, use this form in constructor: var field: Type? = defaultValue
jpa required no argument constructor:
val entity = Person() // Person(name=null, age=null)
there is no code duplication. If you need construct entity and only setup age, use this form:
val entity = Person(age = 33) // Person(name=null, age=33)
there is no magic (just read documentation)