Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin with JPA: default constructor hell

People also ask

Does JPA need default constructor?

The short answer is no.

Does Kotlin have default constructor?

A class needs to have a constructor and if we do not declare a constructor, then the compiler generates a default constructor. A class in Kotlin can have at most one primary constructor, and one or more secondary constructors.

Why does Hibernate need default constructor?

All persistent classes must have a default constructor (which can be non-public) so that Hibernate can instantiate them using Constructor. newInstance() . It is recommended that you have a default constructor with at least package visibility for runtime proxy generation in Hibernate.

Can Kotlin object have constructor?

In Kotlin, a class can have a primary constructor and one or more additional secondary constructors.


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>

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


@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.


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"
}

@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)


I'm a nub myself but seems you have to explicit initializer and fallback to null value like this

@Entity
class Person(val name: String? = null, val age: Int? = null)