Beliebte Suchanfragen
//

Kotlin and Spring: Working with JPA and data classes

15.6.2017 | 7 minutes of reading time

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:

1@Entity
2@Table(name="city")
3internal data class CityEntity(
4   @Id val id: Long? = null,
5   val name: String,
6   val description: String? = null) {
7 
8  fun toDto(): CityDto = CityDto(
9    id = this.id!!,
10    name = this.name,
11    description = this.description)
12 
13  companion object {
14 
15    fun fromDto(dto: CityDto) = CityEntity(
16      id = dto.id,
17      name = dto.name,
18      description = dto.description)
19 
20    fun fromDto(dto: CreateCityDto) = CityEntity(
21      name = dto.name,
22      description = dto.description)
23 
24    fun fromDto(dto: UpdateCityDto, defaultCity: CityEntity) = CityEntity(
25                id = defaultCity.id!!,
26                name = dto.name ?: defaultCity.name,
27                description = dto.description ?: defaultCity.description)
28  }
29}

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. To satisfy this constraint we could define default values for any mandatory parameter or provide a secondary no-arg constructor that calls the primary constructor with predefined default values. There’s a better solution, though: Let’s make use of the JPA compiler plugin : it will generate no-arg constructors for any class annotated with @Entity, @MappedSuperclass or @Embeddable (Thanks to Sébastien for pointing this out).

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:

1@Transactional(Transactional.TxType.MANDATORY)
2internal 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:

1interface CityService {
2 
3  fun retrieveCity(cityId: Long): CityDto?
4 
5  fun retrieveCities(): List<CityDto>
6 
7  fun addCity(city: CreateCityDto): CityDto
8 
9  fun updateCity(id: Long, city: UpdateCityDto): CityDto
10}

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

1@Service
2@Transactional
3internal class JpaCityService(val cityRepo: CityRepository) : CityService {
4 
5  override fun retrieveCity(cityId: Long) : CityDto? {
6    return cityRepo.findOne(cityId)?.toDto()
7  }
8 
9  override fun retrieveCities() : List<CityDto> {
10    return cityRepo.findAll().map { it.toDto() }
11  }
12 
13  override fun addCity(city: CreateCityDto) : CityRepo {
14    return cityRepo.save(CityEntity.fromDto(city)).toDto()
15  }
16 
17  override fun updateCity(id: Long, city: UpdateCityDto): CityDto? {
18    val currentCity = cityRepo.findOne(id)
19    return if (currentCity != null) cityRepo.save(CityEntity.fromDto(city, currentCity)).toDto()
20    else null
21  }
22}

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.

It’s also worth mentioning that similar to the JPA compiler plugin above, we also use the Spring compiler plugin . We need it, because Kotlin classes are final by default, but frameworks like spring need to be able to create proxy classes through inheritance. The Spring compiler plugin will automatically open all classes that use Spring specific annotations like @Component.

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.

1@RunWith(SpringRunner::class)
2@ContextConfiguration(classes = arrayOf(CityConfig::class))
3@DataJpaTest
4internal class JpaCityServiceTest {
5 
6    @Autowired
7    lateinit var service: CityService
8    @Autowired
9    lateinit var repository: CityRepository
10 
11    @get:Rule
12    var softly = JUnitSoftAssertions()
13 
14    @Test
15    fun `'retrieveCities' should retrieve empty list if repository doesn't contain entities`() {
16        assertThat(service.retrieveCities()).isEmpty()
17    }
18 
19    @Test
20    fun `'retrieveCity' should return null if city for cityId doesnt exist`() {
21        assertThat(service.retrieveCity(-99)).isNull()
22    }
23 
24    @Test
25    fun `'retrieveCity' should map existing entity from repository`() {
26        repository.save(CityEntity("cityname", "description"))
27 
28        val result = service.retrieveCity("city")
29        softly.assertThat(result).isNotNull
30        softly.assertThat(result?.id).isNotNull
31        softly.assertThat(result?.name).isEqualTo("cityname")
32        softly.assertThat(result?.description).isEqualTo("description")
33    }
34 
35    @Test
36    fun `'addCity' should return created entity`() {
37        val result = service.addCity(CreateCityDto("name", "description"))
38        softly.assertThat(result.id).isNotNull()
39        softly.assertThat(result.name).isEqualTo("name")
40        softly.assertThat(result.description).isEqualTo("description")
41    }
42 
43    @Test
44    fun `'updateCity' should update existing values`() {
45        val existingCity = repository.save(CityEntity("cityname", "description")).toDto()
46 
47        val result = service.updateCity(existingCity.id, UpdateCityDto("new name", "new description"))
48        softly.assertThat(result).isNotNull
49        softly.assertThat(result?.id).isEqualTo(existingCity.id)
50        softly.assertThat(result?.name).isEqualTo("new name")
51        softly.assertThat(result?.description).isEqualTo("new description")
52    }
53 
54    @Test
55    fun `'updateCity' shouldn't update null values`() {
56        val existingCity = repository.save(CityEntity("cityname", "description")).toDto()
57 
58        val result = service.updateCity(existingCity.id, UpdateCityDto(null, null))
59        softly.assertThat(result).isNotNull
60        softly.assertThat(result?.id).isEqualTo(existingCity.id)
61        softly.assertThat(result?.name).isEqualTo("cityname")
62        softly.assertThat(result?.description).isEqualTo("description")
63    }
64 
65}

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 us 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.

share post

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.