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 } } } }