Search code examples
mongodbkotlinspring-datareactor

SpringBoot ReactiveMongoTemplate updating document partially


I am working on a kotlin reactive spring-boot mongodb project. I'm trying to update a document but it does not work well.

My problem is pretty similar to the following question in stackoverflow.

Spring reactive mongodb template update document partially with objects

So I have a document in mongo

{
  "id": 1,
  "name": "MYNAME",
  "email": "MYEMAIL",
  "encryptedPassword": "12345",
  ...........................
}

And when I call PATCH on the uri localhost:8080/user/1 with the one of following header

{
  "name": "NEW NAME"
}
{
  "email": "NEW EMAIL"
}

I want to update my document with received fields only.

My handler code

fun update(serverRequest: ServerRequest) =
        userService
                .updateUser(serverRequest.pathVariable("id").toLong(), serverRequest.bodyToMono())
                .flatMap {
                    ok().build()
                }

My Service Implement code

override fun updateUser(id: Long, request: Mono<User>): Mono<UpdateResult> {
    val changes = request.map { it -> PropertyUtils.describe(it) }
    val updateFields: Update = Update()
    changes.subscribe {
        for (entry in it.entries) {
            updateFields.set(entry.key, entry.value)
        }
    }
    return userRepository.updateById(id, updateFields)
}

My repository code

    fun updateById(id: Long, partial: Update) = template.updateFirst(Query(where("id").isEqualTo(id)), partial, User::class.java)

My user code

@Document
data class User(
        @Id
        val id: Long = 0,
        var name: String = "",
        val email: String = "",
        val encryptedPassword: ""
)

I have followed the advice that Spring reactive mongodb template update document partially with objects gave.

My code do updates, but it updates to the initial constructor of my User class.

Could anyone help with this?


Solution

  • I figured out how to partially update my data.

    First I changed the body request to string. (using bodyToMono(String::class.java)

    Then I changed the changed JSON string to JSONObject(org.json).

    And for each of its JSONObject's key I created Update that will be the partial data to update my entity.

    Following is how I implemented this.

    override fun updateUser(id: Long, request: Mono<String>): Mono<UpdateResult> {
        val update = Update()
        return request.map { JSONObject(it) }
                .map {
                    it.keys().forEach { key -> update.set(key, it[key]) }
                    update
                }
                .flatMap { it -> userRepository.updateById(id, it) }
    
    }
    

    Please share more idea if you have more 'cleaner' way to do this. Thank you