3
votes

I'm new to Kotlin, and experimenting with spring-data-mongodb. Please see example below (also available here as fully runnable Maven project with in-memory MongoDb: https://github.com/danielsindahl/spring-boot-kotlin-example).

Application.kt

package dsitest

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

@SpringBootApplication
open class Application

fun main(args: Array<String>) {
    SpringApplication.run(Application::class.java, *args)
}

User.kt

package dsitest

import org.springframework.data.annotation.Id
import org.springframework.data.annotation.PersistenceConstructor
import org.springframework.data.mongodb.core.mapping.Document

@Document(collection = "user")
data class User @PersistenceConstructor constructor(@Id val id: String? = null, val userName: String)

UserRepository.kt

package dsitest

import org.springframework.data.repository.CrudRepository

interface UserRepository : CrudRepository<User, String>

KotlinIntegrationTest.kt

package dsitest

import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner

@RunWith(SpringRunner::class)
@SpringBootTest
class KotlinIntegrationTest constructor () {

    @Autowired
    lateinit var userRepository : UserRepository;

    @Test
    fun persistenceTest() {
        val user : User = User(userName = "Mary")
        val savedUser = userRepository.save(user)
        val loadedUser = userRepository.findOne(savedUser.id) // Failing code
        println("loadedUser = ${loadedUser}")
    }
}

When running the test KotlinIntegrationTest.persistenceTest, I get the following error message when trying to retrieve a User object from MongoDb:

org.springframework.data.mapping.model.MappingException: No property null found on entity class dsitest.User to bind constructor parameter to!

If I modify the User data class so that userName is nullable, everything works.

data class User @PersistenceConstructor constructor(@Id val id: String? = null,
val userName: String? = null)

I would like to understand why this is the case, since I don't want userName to be nullable. Is there some alternative, better way of defining my User class in Kotlin?

Many thanks, Daniel

1

1 Answers

5
votes

Yes, it is a known problem. You should check how the bytecode for your User class looks like. Java sees the constructor with all the parameters present and tries to call it with a null value for the 2nd one.

What you could do is to try adding @JvmOverloads to your constructor - this will force Kotlin compiler to generate all versions of the constructor and so the Spring Data Mongo could pick the correct one (get rid of the @PersistenceConstructor) then.

You could also define 1 constructor with no defaults - only for Java-based frameworks and 2nd one with some defaults your you. Or...

When I write things like you are now, I create simple 'persistence' data classes with no default values whatsoever that are mapped to/from my regular domain objects (a sort of abstraction over database). It may generate some overhead at the start - but keeping your domain model not coupled so tightly with the storage model is usually a good idea anyway.