Overview

Kotlin and Spring: Working with JPA and data classes

No Comments

I’ve been looking at Kotlin for a while now and since Spring will support Kotlin as a first class language from version 5 onward I wanted to see how they currently work together.
Being a Java programmer you’re probably familiar with the Java Persistence API: it is used to map database contents to objects. There are different implementations for JPA, the most widely known being Hibernate. It is used in many projects and so I think it’s worthwhile to examine how you can use it through Kotlin and Spring.
A few weeks ago, my colleague Jasper presented his experiences using Kotlin with Spring. I won’t go into the web part again – please refer to Jaspers post for that. Instead I will share my learnings with regard to integrating JPA as well as a few other things I came across along the way.
I’ve simplified the code samples below for better readability, you can access the complete source code here.

Defining JPA entites

In order to integrate JPA with Spring Boot, we start by referencing the corresponding starter module – now we can add some entities.

My sample project provides a way to store, retrieve and update representations of cities. Apart from a technical id, a city consists of a mandatory name and an optional description.
Data classes are one of Kotlin’s treasures: you can use them for classes that mainly hold data and Kotlin will automatically provide methods like equals(), hashCode(), toString(), and copy(). When writing Java code, you can use libraries like Lombok, Immutables or AutoValue to achieve something similar, Kotlin provides this out of the box. We can use data classes alongside the usual JPA annotations to create entities – this is what I came up with:

@Entity
@Table(name="city")
internal data class CityEntity(
   @Id val id: Long? = null,
   val name: String,
   val description: String? = null) {
 
  @Suppress("unused")
  private constructor() : this(name = "")
 
  fun toDto(): CityDto = CityDto(
    id = this.id!!,
    name = this.name,
    description = this.description)
 
  companion object {
 
    fun fromDto(dto: CityDto) = CityEntity(
      id = dto.id,
      name = dto.name,
      description = dto.description)
 
    fun fromDto(dto: CreateCityDto) = CityEntity(
      name = dto.name,
      description = dto.description)
 
    fun fromDto(dto: UpdateCityDto, defaultCity: CityEntity) = CityEntity(
                id = defaultCity.id!!,
                name = dto.name ?: defaultCity.name,
                description = dto.description ?: defaultCity.description)
  }
}

There’s an entity called CityEntity, it is marked as internal because I don’t want it to be visible outside the module it is defined in. I’m using DTOs to transfer data into and out of the module. This way any code using the module doesn’t need to know that JPA is used for persistence.

The entity has a primary constructor that defines the three properties given above, but Hibernate as our JPA provider requires a default-no-arg constructor. We could define default values for any mandatory parameter to satisfy this constraint, but these default values would also take effect when entities are instantiated elsewhere in the code. It’s better to define a secondary no-arg constructor that calls the primary constructor with predefined default values, in this case an empty value for the name parameter. We don’t want anyone else to use this constructor so we make it private – Hibernate will be happy with this. (Note that you cannot define a no-arg primary constructor on data classes)

In order to convert between entities and DTOs, there are a few conversion functions defined on the entity and its companion object: toDto() and some variants of fromDto(). The DTOs are similar to the entity in structure but they provide only the fields necessary for the use case. CreateCityDto doesn’t have the id property for example. Again, please consult Jaspers post for some more refinded examples on how you can use DTOs.

In addition to the entity we now need to define a repository much the same as we would do using Java:

@Transactional(Transactional.TxType.MANDATORY)
internal interface CityRepository : JpaRepository <CityEntity,Long>

Nothing special here, the repository requires a transaction to be present and again is marked as internal, as it should not be visible outside the module. Its functionality is exposed through a service interface, which looks like this:

interface CityService {
 
  fun retrieveCity(cityId: Long): CityDto?
 
  fun retrieveCities(): List<CityDto>
 
  fun addCity(city: CreateCityDto): CityDto
 
  fun updateCity(id: Long, city: UpdateCityDto): CityDto
}

Its JPA specific implementation is again marked as internal, as external code should be dependent on the service interface, not the implementation itself:

@Service
@Transactional
internal class JpaCityService(val cityRepo: CityRepository) : CityService {
 
  override fun retrieveCity(cityId: Long) : CityDto? {
    return cityRepo.findOne(cityId)?.toDto()
  }
 
  override fun retrieveCities() : List<CityDto> {
    return cityRepo.findAll().map { it.toDto() }
  }
 
  override fun addCity(city: CreateCityDto) : CityRepo {
    return cityRepo.save(CityEntity.fromDto(city)).toDto()
  }
 
  override fun updateCity(id: Long, city: UpdateCityDto): CityDto? {
    val currentCity = cityRepo.findOne(id)
    return if (currentCity != null) cityRepo.save(CityEntity.fromDto(city, currentCity)).toDto()
    else null
  }
}

Retrieving and adding new cities is straightforward, but take note how Kotlin makes it very elegant to work with nullable results from the database. Updating takes a little extra work, since the entity is defined to be immutable (see the val properties): after an initial database lookup I’m creating a new entity object from the DTO passed as a parameter using the existing entity for default values. The new object is then saved back to the repository.

Testing

While implementing the classes above we also wrote tests. I’ve added some additional dependencies to the project so that I’m able to use Spring’s test support and AssertJ as an assertion library.

We want our test methods to clearly communicate their intent. In Java based projects, this often results in elaborate camel-case or snake-case constructs. In comparison, Kotlin based tests can read quite nice: You are allowed to use natural-language-like method names if you escape them with backticks. This makes method names look much friendlier, especially when looking at test reports.

@RunWith(SpringRunner::class)
@ContextConfiguration(classes = arrayOf(CityConfig::class))
@DataJpaTest
internal class JpaCityServiceTest {
 
    @Autowired
    lateinit var service: CityService
    @Autowired
    lateinit var repository: CityRepository
 
    @get:Rule
    var softly = JUnitSoftAssertions()
 
    @Test
    fun `'retrieveCities' should retrieve empty list if repository doesn't contain entities`() {
        assertThat(service.retrieveCities()).isEmpty()
    }
 
    @Test
    fun `'retrieveCity' should return null if city for cityId doesnt exist`() {
        assertThat(service.retrieveCity(-99)).isNull()
    }
 
    @Test
    fun `'retrieveCity' should map existing entity from repository`() {
        repository.save(CityEntity("cityname", "description"))
 
        val result = service.retrieveCity("city")
        softly.assertThat(result).isNotNull
        softly.assertThat(result?.id).isNotNull
        softly.assertThat(result?.name).isEqualTo("cityname")
        softly.assertThat(result?.description).isEqualTo("description")
    }
 
    @Test
    fun `'addCity' should return created entity`() {
        val result = service.addCity(CreateCityDto("name", "description"))
        softly.assertThat(result.id).isNotNull()
        softly.assertThat(result.name).isEqualTo("name")
        softly.assertThat(result.description).isEqualTo("description")
    }
 
    @Test
    fun `'updateCity' should update existing values`() {
        val existingCity = repository.save(CityEntity("cityname", "description")).toDto()
 
        val result = service.updateCity(existingCity.id, UpdateCityDto("new name", "new description"))
        softly.assertThat(result).isNotNull
        softly.assertThat(result?.id).isEqualTo(existingCity.id)
        softly.assertThat(result?.name).isEqualTo("new name")
        softly.assertThat(result?.description).isEqualTo("new description")
    }
 
    @Test
    fun `'updateCity' shouldn't update null values`() {
        val existingCity = repository.save(CityEntity("cityname", "description")).toDto()
 
        val result = service.updateCity(existingCity.id, UpdateCityDto(null, null))
        softly.assertThat(result).isNotNull
        softly.assertThat(result?.id).isEqualTo(existingCity.id)
        softly.assertThat(result?.name).isEqualTo("cityname")
        softly.assertThat(result?.description).isEqualTo("description")
    }
 
}

Once JUnit 5 arrives we can achieve something similar in our Java projects using a new feature called Display Names. But once again, Kotlin brings this out of the box and we don’t need to keep method- and display names in-sync.

In my projects I like to use AssertJ as an assertion library and one of its features is a JUnit 4 rule that enables soft-assertions: they give as the ability to check assertions cumulativly at the end of a test and are a handy tool if you don’t want to follow the one assertion per test guideline.
JUnit rules need to be used on public fields or methods, but when writing Kotlin, we are not defining fields on our class, we work with properties: they are essentially a combination of private fields with getters and setters (for mutable properties). If we try to use the required @Rule annotation on a property, Kotlin will apply the annotation on the property’s private backing field, resulting in an error. You can define an annotation target though, here we want the annotation to be used on the property’s public getter: @get:Rule.

Conclusion

Spring Boot aims to simplify application development and teams up nicely with Kotlin. As you can see, in many ways you end up with more concise code. Most people still think Kotlin has its niche in Android development and while it certainly has a strong standing there, there’s no good reason to ignore it for backend development. There’s a few gotcha’s (see the usage of JUnit rules, for example), but so far I’m enjoying the experience and I’m looking forward to Spring 5 and the improved support of Kotlin.

Reinhard Prechtl

Reinhard is an IT consultant and software engineer working for the Nuremberg office of codecentric. For the most part of his career Reinhard has been specializing in JVM technologies and acquired expert knowledge with a variety of frameworks and tools. He has a special interest in software testing, clean code, web based APIs and distributed systems.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Comment

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