0%
August 28, 2025

Spring Data MongoDB

kotlin

springboot

Imports in build.gradle.kts

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
}

Basic Document Definition

1import org.springframework.data.mongodb.core.index.CompoundIndex
2import org.springframework.data.mongodb.core.mapping.Document
3import org.springframework.data.mongodb.core.mapping.Field
4
5
6@Document(collection = "my_collection")
7@CompoundIndex(
8    def = "{'nested.doc.id': 1}", // here 1 means an ascending index
9    name = "any_id_name"
10)

You can specify multiple fields like "{'field1': 1, 'field2': -1}" for compound indexes

11data class SomeMongoClass(
12    @Id // ObjectID field _id will be mapped to id in String using this annotation
13    val published: Boolean
14    val model1s: List<NestedModel1>? = emptyList(),
15    val createdAt: Float?,
16    val updatedAt: Float?,
17) {
18    data class NestedModel1(
19        val model2s: List<NestedModel2>? = emptyList(),
20        val happy: Boolean
21        ...
22    ) {
23        data class NestedModel2(
24            @Field("id")    // with this the mongo sub-document will 
25                            // have _id attribute which is an ObjectId
26            val id: String?= null
27            ...
28        ) : Serializable
29    }
30}
31

Basic Repository

import org.bson.types.ObjectId
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.Query


interface SomeMongoClassRepository : MongoRepository<SomeMongoClass, ObjectId> {
    @Query(value = "{ \$or: [{ 'model1s.model2s.id' : { \$in: ?0 } }, ... ] }")
    fun findAllByResultSummaryUuidIn(resultSummaryUuids: List<String>): MutableList<SomeMongoClass>
}

Here we wrap the custom query by @Query. For complex aggregation pipeline we can ask LLM model for help.

At this point we can create standard query in this interface by naming convention such as

    fun findByCreatedAt(...): SomeMongoClass

When the setup is correct the function name can be auto-completed.

Query for Documents with Dramatically Varying Schema

Problem

As the project grows, our schema may change dramatically due to business growth. It is inevitable to manage old data that failed to be parsed into SomeMongoClass.

As long as one result cannot be parsed into SomeMongoClass, query generated from the interface SomeMongoClassRepository via spring-data mongo will always fail.

Solution
  • To tackle this problem we need to query for results regardless of its schema (namely, we fetch Document's).

  • We then parse the documents into desired data type from our code and handle the exception gracefully.

class SomeQueryService (
    private val mongoTemplate: MongoTemplate,
) {
    private fun getResultsAfter(afterTimestamp: Long): List<SomeMongoClass> {
        val query = Query.query(
            Criteria().orOperator(
                Criteria.where("createdAt").exists(true).gt(afterTimestamp),
                Criteria.where("updatedAt").exists(true).gt(afterTimestamp)
            )
        )
        return mongoTemplate.find(query, Document::class.java, "my_collection")
            .mapNotNull { doc ->
                try {
                    mongoTemplate.converter.read(SomeMongoClass::class.java, doc)
                } catch (e: MappingInstantiationException) {
                    println("Skipping document with mapping issues: ${doc.getObjectId("_id")}")
                    null
                } catch (e: Exception) {
                    println(e)
                    null
                }
            }
    }
}