DEV Community

Cover image for Defining JPA/Hibernate Entities in Kotlin
Antonello Zanini for Writech

Posted on • Edited on

Defining JPA/Hibernate Entities in Kotlin

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

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

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

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

}
Enter fullscreen mode Exit fullscreen mode
@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);
    }
}
Enter fullscreen mode Exit fullscreen mode

What about Kotlin?

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

Let's try…

@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()
)
Enter fullscreen mode Exit fullscreen mode
@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()
)
Enter fullscreen mode Exit fullscreen mode

In this way, we can avoid boilerplate code, but according to the Spring Official Guide you shouldn't use data classes_._

We don't use data classes 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 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.

@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
    }
}
Enter fullscreen mode Exit fullscreen mode
@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
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Extra 2

Defining JPA entities are strongly correlated with JPA 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 will teach you that.


The post "Defining JPA/Hibernate Entities in Kotlin" appeared first on Writech.

Top comments (0)