As of today, working with data is essential in software development. Database management is one of the key aspects that every developer must master.
MongoDB is a NoSQL (non-relational) database management system that is based on a document model. Unlike traditional relational databases, MongoDB stores data in flexible documents similar to JSON, allowing for greater agility and scalability in data management. MongoDB is known for its ability to scale horizontally, meaning it can handle large volumes of data distributed across multiple servers. Additionally, it offers features such as flexible queries, efficient indexing, and automatic replication to ensure availability and fault tolerance. MongoDB is widely used in web, mobile, and data analytics applications.
In this topic, we will use MongoDB with Kotlin to learn how to process our data with this database.
Install and configure MongoDB driver
The MongoDB Kotlin driver supports "native" object mapping and also Kotlin serialization. If we want to use kotlinx.serialization, we need to include it as a plugin in build.gradle.kts:
plugins {
kotlin("jvm") version "1.9.0"
application
// To use Kotlinx Serialization is not mandatory
kotlin("plugin.serialization") version "1.9.0"
}
The MongoDB Kotlin driver has a synchronous version and a coroutine version to work in an asynchronous way.
Then we will include the MongoDB dependency in the "dependencies" block.
dependencies {
// Kotlin coroutine dependency, if you want to use async mode
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
// MongoDB Kotlin driver dependency
implementation("org.mongodb:mongodb-driver-kotlin-coroutine:4.10.1")
testImplementation(kotlin("test"))
}Create a connection
We need to create a client with the URL of our database and get the database and the collection for working with the documents. Finally, we have to close the connection. We can type the collection. We'll use @BsonId to note that thу field will be of type ObjectId, which represents the internal _id of any document in a collection in MongoDB and is unique.
data class Person(@BsonId val id: ObjectId, val name: String, val age: Int)
// Connect to MongoDB
val mongoClient = MongoClient.create(MONGO_DB_URL)
val database = mongoClient.getDatabase("persons")
// Get a collection of documents of type Person
val collection = database.getCollection<Person>("persons")
//...
// Close connection
mongoClient.close()Working with MongoDB
1. Create
You can create one item with insertOne or many items with insertMany. You can use the ObjectId() function to generate the MongoDB _id value.
// Create one
val newPerson = Person(ObjectId(), "Joe Doe", 36)
collection.insertOne(newPerson).also {
println("Person added with id: ${it.insertedId}")
}
// Person added with id: BsonObjectId{value=64e4e2158c887a05f4ea7b5b}
// Create many
val morePersons = listOf(
Person(ObjectId(), "Jane Doe", 25),
Person(ObjectId(), "John Doe", 27)
)
collection.insertMany(morePersons).also {
println("Persons added with ids: ${it.insertedIds}")
}
// Persons added with ids: {0=BsonObjectId{value=64e4e2168c887a05f4ea7b5c},
// 1=BsonObjectId{value=64e4e2168c887a05f4ea7b5d}}
2. Read
Just call the find function. If we need a filter, we can create a predicate to find by a field.
// Get All
val persons = collection.find().toList()
println("Persons count: ${persons.size}")
println("Persons list")
persons.forEach {
println("${it.name} - ${it.age}")
}
// Persons list
// Joe Doe - 36
// Jane Doe - 25
// John Doe - 27
// Find one by name
val person = collection.find(eq("name", "Jane Doe")).firstOrNull()?.let {
println("Person found: ${it.name} - ${it.age}")
} ?: println("Person not found")
// Person found: Jane Doe - 25
3. Update
Use the updateOne or updateMany functions with a filter and the update options.
// Update one
collection.updateOne(eq("name", "Jane Doe"), set("age", 26)).also {
println("Person updated: ${it.modifiedCount}")
}
// Person updated: 1
// Update many
collection.updateMany(eq("name", "John Doe"), set("age", 28)).also {
println("Persons updated: ${it.modifiedCount}")
}
// Persons updated: 1
6. Delete
Use the deleteOne or deleteMany functions with a filter option.
// Delete one
collection.deleteOne(eq("name", "Jane Doe")).also {
println("Person deleted: ${it.deletedCount}")
}
// Person deleted: 1
// Delete many
collection.deleteMany(eq("name", "John Doe")).also {
println("Persons deleted: ${it.deletedCount}")
}
// Persons deleted: 1
6. Watch for changes
In MongoDB, you can monitor data changes by using change streams. By opening a change stream, applications can observe modifications to collections, databases, or deployments and respond accordingly with watch(). Change streams provide a stream of change event documents that are generated when changes happen. These change event documents contain details about the updated data. Also, we can filter the type of change or event. Please note that this feature is only valid if you have a MongoDB Server with replicas.
val changeStream = collection.watch()
changeStream.collect {
println("Change observed: $it")
}
7. Count documents
In the MongoCollection class, there are two methods available to count the number of documents in a collection:
-
countDocuments(): This method provides an accurate count of the documents in the collection that match a specified query. If you pass an empty query filter, it returns the total count of all documents in the collection. -
estimatedDocumentCount(): This method gives an estimation of the document count based on the collection's metadata. It doesn't allow specifying a query.
The estimatedDocumentCount() method is faster because it relies on metadata rather than scanning the entire collection. However, it provides an estimate, while countDocuments() gives an accurate count and supports query filters.
// Count
val count = collection.countDocuments()
println("Persons count: $count")
// Persons count: 1
// Estimated count
val estimatedCount = collection.estimatedDocumentCount()
println("Persons estimated count: $estimatedCount")
// Persons estimated count: 1
8. Distinct documents
To obtain a list of unique values for a specific field in a collection, you can use the distinct() method provided by the MongoCollection class. This method requires two parameters: the name of the field you want to retrieve distinct values for and the desired type to cast the results to. By calling this method, you can retrieve a distinct list of values for the specified field from the collection.
val distinct = collection.distinct<Int>(
Person::name, Filters.eq(Person::name, "Joe Dow")
)
resultsFlow.collect {
println("${it.name} - ${it.age}")
}
// Joe Doe - 36
9. Drop a collection
We can drop all the documents or a collection using the drop() function. Dropping a collection essentially removes all data and metadata related to that collection, making it no longer accessible within the database.
// Drop
collection.drop().also {
println("Collection dropped")
}
// Collection droppedConclusion
In this topic, we have learned how to use NoSQL databases with Kotlin and MongoDB. Now you can handle databases easily with type-safe code in a sync or async way.
Now is the time to do some tasks to check what you have learned. Are you ready?