Defining JPA/Hibernate Entities in Kotlin
Tags: hibernate, jpa, kotlin, spring
Published: 2024-01-19 20:54:23

Defining JPA entities with Hibernate is really easy and well-documented, especially in Java, but what if we wanted to do it in Kotlin?

Let's assume we have a database like the following:

![ER model of the database](https://writech.run/wp-content/uploads/2023/09/image-15.png)

Each `Post` has a title and each `Tag` has a `name`.

We can map these two entities in Java with the following code:

```java
@Entity
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long id;

    @Column(name = "title")
    private String title;

    @ManyToMany
    @JoinTable(name = "post_tag",
            joinColumns = @JoinColumn(name = "post_id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private Set<Tag> tags = new HashSet<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Set<Tag> getTags() {
        return tags;
    }

    public void setTags(Set<Tag> tags) {
        this.tags = tags;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Post tag = (Post) o;
        return Objects.equals(id, tag.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

}
```

```kotlin
@Entity
@Table(name = "tag")
public class Tag {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    @ManyToMany(mappedBy = "tags")
    private Set<Post> posts = new HashSet<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Post> getPosts() {
        return posts;
    }

    public void setPosts(Set<Post> posts) {
        this.posts = posts;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Tag tag = (Tag) o;
        return Objects.equals(id, tag.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
```

What about Kotlin?

The first idea we might have is to map the two entities into simple **data classes**.

Let's try…

```kotlin
@Entity
@Table(name = "post")
data class Post(
    @get:Id
    @get:GeneratedValue
    @get:Column(name = "id")
    val id: Long,

    @get:Column(name = "title")
    val title: String,

    @get:ManyToMany
    @get:JoinTable(
            name = "post_tag",
            joinColumns = [JoinColumn(name = "post_id")],
            inverseJoinColumns = [JoinColumn(name = "tag_id")]
    )
    val tags: Set<Tag> = HashSet()
)
```

```kotlin
@Entity
@Table(name = "tag")
data class Tag(
    @get:Id
    @get:GeneratedValue
    @get:Column(name = "id")
    val id: Long,

    @get:Column(name = "name")
    val name: String,

    @get:ManyToMany(mappedBy = "tags")
    val posts: Set<Post> = HashSet()
)
```

In this way, we can avoid boilerplate code, but according to the [Spring Official Guide](https://github.com/spring-guides/tut-spring-boot-kotlin#persistence-with-jpa) you shouldn't use data classes_._

> _We don't use [`data classes`](https://kotlinlang.org/docs/reference/data-classes.html) with `val` properties because JPA is not designed to work with immutable classes or the methods generated automatically by `data classes`. If you are using other Spring Data flavor, most of them are designed to support such constructs so you should use classes like `data class User(val login: String, …​)` when using Spring Data MongoDB, Spring Data JDBC, etc._

So, how can we define entities in Kotlin?

In order to define working entities, following the [Hibernate User Guide](https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html) is highly recommended. These are some rules we are required to respect:

- An entity class must have a no-argument constructor, which may be public, protected, or package visibility. It may define additional constructors as well.

- An entity class needs not to be a top-level class.

- Technically Hibernate can persist final classes or classes with final persistent state accessor (getter/setter) methods. However, it is generally not a good idea as doing so will stop Hibernate from being able to generate proxies for lazy-loading the entity.

- Hibernate does not restrict the application developer from exposing instance variables and referencing them from outside the entity class itself. The validity of such a paradigm, however, is debatable at best.

Don't forget that each accessor method should be declared `_open_`, since **by default Kotlin classes are final**.

Why can't we use final classes?

> _A central feature of Hibernate is the ability to load lazily certain entity instance variables (attributes) via runtime proxies. **This feature depends upon the entity class being non-final** or else implementing an interface that declares all the attribute getters/setters. You can still persist final classes that do not implement such an interface with Hibernate, but **you will not be able to use proxies for fetching lazy associations**, therefore limiting your options for performance tuning. For the very same reason, **you should also avoid declaring persistent attribute getters and setters as final**._

In conclusion, here is how to define working JPA entities with Hibernate in Kotlin.

```kotlin
@Entity
@Table(name = "post")
open class Post {

    @get:Id
    @get:GeneratedValue
    @get:Column(name = "id")
    open var id: Long? = null

    @get:Column(name = "title")
    open var title: String? = null

    @get:ManyToMany
    @get:JoinTable(
            name = "post_tag",
            joinColumns = [JoinColumn(name = "post_id")],
            inverseJoinColumns = [JoinColumn(name = "tag_id")]
    )
    open var tags: Set<Tag> = HashSet()

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || javaClass != other.javaClass) return false
        val that = other as Post
        if (id != that.id) return false
        return true
    }

    override fun hashCode(): Int {
        return if (id != null)
            id.hashCode()
        else 0
    }
}
```

```kotlin
@Entity
@Table(name = "tag")
open class Tag {
    
    @get:Id
    @get:GeneratedValue
    @get:Column(name = "id")
    open var id: Long? = null

    @get:Column(name = "name")
    open var name: String? = null

    @get:ManyToMany(mappedBy = "tags")
    var posts: Set<Post> = HashSet()

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || javaClass != other.javaClass) return false
        val that = other as Tag
        if (id != that.id) return false
        return true
    }

    override fun hashCode(): Int {
        return if (id != null)
            id.hashCode()
        else 0
    }
}
```

## Bonus

### Extra 1

What if we didn't want to declare all entity classes and their members as `open`?

We can use the Kotlin `allopen` plugin as described [here](https://github.com/spring-guides/tut-spring-boot-kotlin#persistence-with-jpa).

### Extra 2

Defining JPA entities are strongly correlated with [JPA criteria queries](https://www.baeldung.com/hibernate-criteria-queries). The next step is to **learn how to use these entities in a type-safe way**, while building robust criteria queries. The following [article](https://writech.run/blog/criteria-queries-and-jpa-metamodel-with-spring-boot-and-kotlin-9c82be54d626) will teach you that.
  
***
_The post "[Defining JPA/Hibernate Entities in Kotlin](https://writech.run/blog/defining-jpa-hibernate-entities-in-kotlin-1ff8ee470805/)" appeared first on [Writech](https://writech.run)._