Spring Boot, Hibernate and Kotlin for beginners step by step

Hello everyone, my name is Oleg, I’m a technical expert in DomKlik. In our team, the kernel of the stack is Kotlin and Spring Boot. I want to share with you my experience on the interaction and the features of working with PostgreSQL and Hibernate in conjunction with Spring Boot and Kotlin. Also, using the example of a microservice, I will show the advantages of Kotlin and its differences from a similar Java application. I’ll talk about the not-so-obvious difficulties that newcomers may encounter when using this stack with Hibernate. This article will be useful for developers who want to switch to Kotlin and are familiar with Spring Boot, Hibernate Java.

Plugins

For the application on Kotlin, as the project collector, take Gradle Kotlin DSL. The list of connected plugins will be standard for Spring Boot, and for Kotlin with Hibernate we will have several new ones:

plugins {
    id("org.springframework.boot") version "2.2.7.RELEASE"
    id("io.spring.dependency-management") version "1.0.9.RELEASE"
    kotlin("jvm") version "1.3.72"
    kotlin("plugin.spring") version "1.3.72"
    kotlin("plugin.jpa") version "1.3.72"
}

Consider the last three.

kotlin(«jvm») – The basic Kotlin plugin for working on the JVM. Without which not a single application on the Java stack will start.

kotlin(«plugin.spring») – since classes in Kotlin are final by default, this plugin will automatically make classes marked with annotations @Component, @Async, @Transactional, @Cacheable and @SpringBootTest open to inheritance, and in the subject matter of this article, this will allow classes written in Kotlin to be proxied in Spring through a CGLib proxy.

It’s important to note that entities marked with annotations @Entity, @MappedSuperclass and @Embaddablewill not open after connecting the plugin. Moreover, get accessor’S will also be final, and then we will lose the opportunity to work with entity reference. To avoid this and do Entity and its fields open, add to build.gradle.kts:

allOpen {
   annotation("javax.persistence.Entity")
   annotation("javax.persistence.MappedSuperclass")
   annotation("javax.persistence.Embeddable")
}

kotlin(«plugin.jpa») – If the previous two plugins are applied to any application on Kotlin + Spring Boot, then the next one is already directly related to Hibernate. And he, as you know, for initialization Entity uses reflection and initializes a class with a constructor with no arguments. But since we write in Kotlin, such a constructor may not be found. If we defined our own primary constructor (primary constructor), then at boot Entity we will throw an exception:

org.hibernate.InstantiationException: No default constructor for entity

Dependencies

The set of dependencies will also not be completely identical to the set in Java:

dependencies {
  implementation("org.springframework.boot:spring-boot-starter-web")
  implementation("org.springframework.boot:spring-boot-starter-data-jpa")
  implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
  implementation("org.jetbrains.kotlin:kotlin-reflect")
  implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
  implementation("org.liquibase:liquibase-core")

  runtimeOnly("org.postgresql:postgresql")

  testImplementation("org.springframework.boot:spring-boot-starter-test")
  testImplementation("org.testcontainers:testcontainers:$testContainersVer")
  testImplementation("org.testcontainers:postgresql:$testContainersVer")
}

Add a couple of dependencies in addition to the standard Spring Boot web starter and the main starter that interests us org.springframework.boot:spring-boot-starter-data-jpa, which as the default JPA implementation pulls Hibernate:

org.jetbrains.kotlin:kotlin-reflect – needed for reflection on Kotlin, which is already supported in Spring Boot and is widely used to initialize classes.

org.jetbrains.kotlin:kotlin-stdlib-jdk8 – Adds the ability to work with Java collections, stream support and much more.

This is where the differences in configuring a Kotlin project compared to Java end with us, let’s move on to the project itself, its structure of tables and entities.

Tables and Entities

Our application will consist of two department and employee tables, which are linked by a one-to-many relationship.

Table structure:

As a base we will use the DBMS PostgreSQL. Create the table structure using liquibase, and as test dependencies we will use the standard starter:

org.springframework.boot:spring-boot-starter-test – we will test in Docker using testcontainers.

Entities

As with any application with more than one entity, create a common ancestor for everyone entity.

BaseEntity:

@MappedSuperclass
abstract class BaseEntity {

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

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

       if (this === other) return true

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

       other as BaseEntity<*>

       return this.id != null && this.id == other.id
   }

   override fun hashCode() = 25

   override fun toString(): String {
       return "${this.javaClass.simpleName}(id=$id)"
   }
}

DepartmentEntity:

@Entity
@Table(name = "department")
class DepartmentEntity(

       val name: String,

       @OneToMany(
               mappedBy = "department",
               fetch = FetchType.LAZY,
               orphanRemoval = true,
               cascade = [CascadeType.ALL]
       )
       val employees: MutableList = mutableListOf()
) : BaseAuditEntity() {

   fun addEmployee(block: DepartmentEntity.() -> EmployeeEntity) {
       employees.add(block())
   }

   fun setEmployees(block: DepartmentEntity.() -> MutableSet) {
       employees.clear()
       employees.addAll(block())
   }
}

EmployeeEntity:

@Entity
@Table(name = "employee")
class EmployeeEntity(

       val firstName: String,

       var lastName: String? = null,

       @ManyToOne
       @JoinColumn(name = "department_id")
       val department: DepartmentEntity
) : BaseAuditEntity()

We do not use data classes. This seems like a clear advantage to Kotlin over Java (prior to version 14), and there is an explanation for this.

Why not use?

Data classes, in addition to being final in themselves, have certain fields for all fields equals, hashCode and toString. And this unacceptable in conjunction with Hibernate.

Why? And also why hashCode always equal to constant – the answer is in Hibernate documentation. Specifically, we are interested in this section:

Although using a natural-id is best for equals and hashCode, sometimes you only have the entity identifier that provides a unique constraint.

It’s possible to use the entity identifier for equality check, but it needs a workaround:

  • you need to provide a constant value for hashCode so that the hash code value does not change before and after the entity is flushed.
  • you need to compare the entity identifier equality only for non-transient entities.

That is, you need to compare either natural id, or, as in our example, by primary key id. This will avoid a lot of problems when comparing the entity and save from its loss when using the entity as an element in Set.

The presence of toString, defined by all fields, completely kills all laziness, for example, when journalizing an entity, since all fields will be initialized for output to a string.

Given the features of Hibernate, this Kotlin functionality is not suitable for us.

Class constructor

Kotlin allows you to set variables through the constructor, which is a sin not to use. Consider again DepartmentEntity:

class DepartmentEntity(

       val name: String,

       @OneToMany(
               mappedBy = "department",
               fetch = FetchType.LAZY,
               orphanRemoval = true,
               cascade = [CascadeType.ALL]
       )
       val employees: MutableList = mutableListOf()
) : BaseAuditEntity() {

We can also initialize the unit name through the constructor, for example:

departmentRepository.save(DepartmentEntity(name = "Department One"))

Through the constructor, you can initialize, among other things, the list of employees employees. Collections, of course, will be declared mutable.

Use var / val depending on the need to change the field

We marked the name of the organization as val:

class DepartmentEntity(

       val name: String,

and it cannot be null.

The choice var/val is a convenient option and depends on the business logic. Choose between var and val it is necessary proceeding from the requirement: whether the entity field should be mutable.

Null validity in fields according to DB only

As for the validity of values null everything is not so simple in the fields. Earlier we plunged a bit into the depths of Hibernate: speaking of plugin.jpa, I mentioned the use of a constructor with no arguments when initializing an entity.

When initializing fields, reflection is also used. And if the value in the corresponding column was stored in the database null, then the class is quietly initialized with this field with the value null. When accessing it, we run the risk of getting NPE, although the field is marked as not nullable. To avoid this, it is necessary to monitor the synchronism of the structure of tables and classes.

If you look at what is described in the last two sections more comprehensively, then these rules apply not only to primitives, but also to a bunch of entities.

For instance, EmployeeEntity always attached to DepartmentEntity:

class EmployeeEntity(

       val firstName: String,

       var lastName: String? = null,

       @ManyToOne
       @JoinColumn(name = "department_id")
       val department: DepartmentEntity
) : BaseAuditEntity()

Department is not null and it cannot be changed, which can save you from all kinds of errors, especially if business logic requires immutability.

Repositories

When using Kotlin, repositories out of the box now have a validation check null. So, if we are sure that when searching department by name the result will be unique and unique, then you can specify the return type as non nullable:

interface DepartmentRepository : JpaRepository {

   fun findOneByName(name: String) : DepartmentEntity
}

Here DepartmentEntity specified sole and cannot be null. If for some reason we did not find the desired department, then we’ll catch not NPE, but something else:

org.springframework.dao.EmptyResultDataAccessException: Result must not be null!

This processing is achieved by adding specialized Kotlin support to MethodInvocationValidator and ReflectionUtils in spring data commons.

lateinit var

Another Kotlin feature that I would like to consider is lateinit var.

Add a new ancestor class: BaseAuditEntity.

@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
abstract class BaseAuditEntity : BaseEntity() {

   @CreatedDate
   @Column(updatable = false, nullable = false)
   lateinit var created: LocalDateTime

   @LastModifiedDate
   @Column(nullable = false)
   lateinit var modified: LocalDateTime
}

Consider application lateinit var on the example of audit fields (created, modified)

lateinit var – this not null field with delayed initialization. Access to the field before its initialization generates an error:

kotlin.UninitializedPropertyAccessException: lateinit property has not been initialized

As a rule, we refer to the fields created and modified after the entity has been stored in the database. In our case, the data in these fields are supplied at the save stage and they not nullthen lateinit var more than suitable for us.

Summary

We created an application that took into account many of the benefits of Kotlin, and looked at the important differences from Java, avoiding many hidden surprises. I would be glad if this article is useful not only for beginners. Later we will continue the topic of microservice communication with the database.

Application Link.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *