How to deal with Hibernate entities in Kotlin

Spring Boot officially supports Kotlin. It works perfectly for almost everything except Spring Data JPA. To be realistic, that is not even a Spring issue. It’s mainly related to how Hibernate entities work. Since Spring Data JPA uses Hibernate by default under the hood, the issue became a Spring problem. In this tutorial, we explain how to deal with Hibernate entities in Kotlin.

The problem with Hibernate entities

The root of the issue between Hibernate entities and Kotlin can be summarized in one word, immutability. By default, everything in Kotlin is immutable. That means that once you assign a value to a variable or an object, you can’t change it. Therefore, to construct an object, values must be passed to the constructor. That is a good practice as it reduces bugs by removing side effects from the codebase. However, Hibernate entities must be mutable. Don’t ask why. It is how Hibernate is designed and functions.

Additionally, Hibernate heavily relies on reflection. If an entity class is final, it fails miserably, and Kotlin classes are final by default. Hence, just a simple data class does not work with Hibernate.

The simple, yet clean solution

Even though making Hibernate work with Kotlin seems tedious, there’s a simple solution to tackle the issue. We studied and tried many approaches. We concluded that Simon Wirtz’s solution in Hibernate with Kotlin – powered by Spring Boot is the most elegant approach. Thus we adapted with some modifications and adjustments.

Simon Wirtz presented different approaches, yet in his last solution, he suggested not using data class at all and instead using open Kotlin classes for Hibernate entities. And create an abstract Hibernate class to deal with ID, equals, toString, and hashCode. That is a perfect solution, except for adding many plugins to the project to modify the default behavior of the language and generate code.

Hence, after some testing, we concluded that the no-args and allopen plugins are redundant. It works successfully as if with Kotlin 1.2.71 and above. Here is a Hibernate entity sample,

package com.madadipouya.example.multiple.datasource.entity;

import org.springframework.data.domain.Persistable
import org.springframework.data.util.ProxyUtils
import java.io.Serializable
import jakarta.persistence.*

/**
 * Abstract base class for entities. Allows parameterization of id type, chooses auto-generation and implements
 * [equals] and [hashCode] based on that id.
 *
 * This class was inspired by [org.springframework.data.jpa.domain.AbstractPersistable], which is part of the Spring Data project.
 */
@MappedSuperclass
abstract class AbstractJpaPersistable<T : Serializable> : Persistable<T> {

    companion object {
        private val serialVersionUID = -5554308939380869754L
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private var id: T? = null

    override fun getId(): T? {
        return id
    }

    /**
     * Must be [Transient] in order to ensure that no JPA provider complains because of a missing setter.
     *
     * @see org.springframework.data.domain.Persistable.isNew
     */
    @Transient
    override fun isNew() = null == getId()

    override fun toString() = "Entity of type ${this.javaClass.name} with id: $id"

    override fun equals(other: Any?): Boolean {
        other ?: return false

        if (this === other) return true

        if (javaClass != ProxyUtils.getUserClass(other)) return false

        other as AbstractJpaPersistable<*>

        return if (null == this.getId()) false else this.getId() == other.getId()
    }

    override fun hashCode(): Int {
        return 31
    }
}

The credit for the first code snippet, AbstractJpaPersistable, goes to Simon Wirtz. We just modified the generated type to identified instead of Hibernate sequence.

Well, that’s not the end of it. We will do further experiments and see whether removing the mentioned two plugins has any adverse effect. If we identify anything, we will update it in a new post and cross-reference it here.

Conclusion

In this article, we covered how to deal with Hibernate entities in Kotlin since by default all classes are immutable in Kotlin. The best and the most elegant approach is to create an abstract class containing fields like id with hashCode, and toString methods and then inherit that class in entities. As of Kotlin 1.2.71, there is no need for any external library to achieve it.

Inline/featured images credits