MongoDB
MongoDB et permet gestionar dades documentals.
Introducció
https://gitlab.com/xtec/kotlin/mongodb
Entorn de treball
Crea la carpeta mongodb i entra a la carpeta:
> md mongodb
> cd mongodbExecuta gradle init amb els paràmetres que es mostren a continuació per generar una aplicació Kotlin amb el nom mongodb:
gradle init --package mongodb --project-name mongodb --java-version 21 --type kotlin-application --dsl kotlin --test-framework kotlintest --no-split-project --no-incubating --overwriteModifica el fitxer app/build.gradle.kts:
dependencies {
// ...
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
implementation("org.mongodb:mongodb-driver-kotlin-coroutine:5.3.0")
}Modifica el fitxer App.kt:
package mongodb
import com.mongodb.kotlin.client.coroutine.MongoClient
import kotlinx.coroutines.runBlocking
import org.bson.BsonInt64
import org.bson.Document
fun main() = runBlocking {
val client = MongoClient.create("mongodb://localhost:27017")
val database = client.getDatabase("pets")
// Provem que tenim connexió
database.runCommand(Document("ping", BsonInt64(1)))
println("Successfully connected to MongoDB")
client.close()
}La connexió amb la nostra base de dades es pot dividir en dos passos:
- Primer, creem una instància de MongoClient amb la Connection URI.
- I en segon lloc, fem servir el client per connectar-nos amb la base de dades
pets.
Executa l’aplicació.
Si no tens arrencada la base de dades MongoDB, al cap d’una estona tens un error de tipus MongoTimeoutException:
Exception in thread "main" com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches ReadPreferenceServerSelector{readPreference=primary}. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.io.IOException: The remote computer refused the network connection}}]
...…
Arrenca la base de dades local tal com s’explica a {% link “p:/data/mongodb/basics/” %} i verifica que el codi funciona correctament
> mogodbInsert
Crea una classe de dades Dog:
data class Dog(
@BsonId
val id: ObjectId,
val name: String
)L’anotació @BsonId representa la identitat única o _id del document.
A continuació insereix un gos amb el mètode insertOne():
fun main() = runBlocking {
// ...
val collection = database.getCollection<Dog>("dogs")
val result = collection.insertOne(Dog(ObjectId(),"Trufa"))
println(result.insertedId)
}Com que la col·lecció dogs encara no existeix, el servidor la crea automàticament.
Si la inserció és correcta, insertOne() retorna una instància d’InsertOneResult que et permet recuperar informació com el camp _id del document que has inserit amb insertId.
Si l’operació d’inserció falla, el controlador llança una excepció.
A continuació insereix diversos gossos en una sola operació amb el mètode insertMany().
fun main() = runBlocking {
// ...
val collection = database.getCollection<Dog>("dogs")
collection.drop()
val dogs = listOf(Dog(ObjectId(), "Trufa"), Dog(ObjectId(), "Ketzu"),
Dog(ObjectId(), "Marilyn"))
val result = collection.insertMany(dogs)
println(result.insertedIds)
}Per evitar tenir documents duplicats, primer eliminem la col·lecció.
Després d’una inserció correcta, insertMany() retorna una instància d’InsertManyResult.
Find
Modifica la classe de dades Dog per incloure la raça del gos:
data class Dog(
@BsonId
val id: ObjectId,
val name: String,
val breed: String?
)El mètode find() et permet cercar documents en una col·lecció.
Si no hi inclous cap filtre, el mètode find() retorna un cursor que itera tots els elements de la col·lecció:
// ...
collection.insertMany(
listOf(
Dog(ObjectId(), "Trufa", "Rough Collie"),
Dog(ObjectId(), "Ketzu", "Shiba Inu"),
Dog(ObjectId(), "Marilyn", null),
Dog(ObjectId(), "Duc", "Rough Collie"),
Dog(ObjectId(), "Milo", "Border Collie"),
)
).also { println(it.insertedIds)}
collection.find<Dog>().collect { println(it) }Pots veure que tots els gossos s’han desat a la base de dades:
Successfully connected to MongoDB
{0=BsonObjectId{value=67853f94ab522d5b4fa362b2}, 1=BsonObjectId{value=67853f94ab522d5b4fa362b3}, 2=BsonObjectId{value=67853f94ab522d5b4fa362b4}, 3=BsonObjectId{value=67853f94ab522d5b4fa362b5}, 4=BsonObjectId{value=67853f94ab522d5b4fa362b6}}
Dog(id=67853f94ab522d5b4fa362b2, name=Trufa, breed=Rough Collie)
Dog(id=67853f94ab522d5b4fa362b3, name=Ketzu, breed=Shiba Inu)
Dog(id=67853f94ab522d5b4fa362b4, name=Marilyn, breed=null)
Dog(id=67853f94ab522d5b4fa362b5, name=Duc, breed=Rough Collie)
Dog(id=67853f94ab522d5b4fa362b6, name=Milo, breed=Border Collie)El mètode find() retorna una instància de FindFlow, una classe que ofereix diversos mètodes per accedir, organitzar i recórrer els resultats, com ara first() i firstOrNull().
El mètode firstOrNull() retorna el primer document dels resultats recuperats o null si no hi ha resultats.
import kotlinx.coroutines.flow.firstOrNull
// ...
val dog = collection.find<Dog>(eq(Dog::name.name,"Lassie")).firstOrNull()
println(dog?.name)El mètode first() retorna el primer document o llança una excepció NoSuchElementException si cap document no coincideix amb la consulta.
import com.mongodb.client.model.Filters.eq
import kotlinx.coroutines.flow.first
// ...
val dog = collection.find<Dog>(eq(Dog::name.name,"Lassie")).first()
println(dog.name)Filter
Els filtres són operacions que s’utilitzen per limitar els resultats d’una consulta segons unes condicions especificades.
La classe Filters proporciona mètodes estàtics per a tots els operadors de consulta de MongoDB. Cada mètode retorna una instància del tipus BSON, que pots passar a qualsevol mètode que esperi un filtre de consulta.
import com.mongodb.client.model.Filters.*Per exemple, pots cercar tots els gossos de raça “Rough Collie” amb l’operador eq:
collection.find<Dog>(eq(Dog::breed.name, "Rough Collie")).collect { print("${it.name} ") }O tots els gossos de raça “Rough Collie” o “BorderCollie” amb l’operador eq:
collection.find<Dog>(
or(
listOf(
eq(Dog::breed.name, "Rough Collie"), eq(Dog::breed.name, "Border Collie")
)
)
).collect { print("${it.name} ") } // Milo Trufa Duc Lassie Encara que aquest exemple quedaria millor amb l’operador in, ja que estem filtrant la mateixa propietat:
collection.find<Dog>(
`in`(Dog::breed.name, listOf("Rough Collie", "Border Collie"))
).collect { print("${it.name} ") } // Milo Trufa Duc LassiePaginació
Amb els operadors limit i skip pots afegir paginació als resultats:
// ...
collection.find<Dog>().limit(2).collect { print("${it.name} ") }
collection.find<Dog>().limit(2).skip(2).collect { print("${it.name} ") }
// Trufa Ketzu Marilyn Duc Però amb aquest enfocament, sovint, el temps de resposta de la consulta augmenta amb el valor de l’offset.
Per superar-ho, ens podem beneficiar de crear un Index, tal com es mostra a continuació:
// ...
val collection = database.getCollection<Dog>("dogs")
collection.drop()
collection.createIndex(
keys = Indexes.ascending(Dog::breed.name)
)
collection.insertMany(
listOf(
Dog(ObjectId(), "Trufa", "Rough Collie"),
Dog(ObjectId(), "Ketzu", "Shiba Inu"),
Dog(ObjectId(), "Marilyn", null),
Dog(ObjectId(), "Duc", "Rough Collie"),
Dog(ObjectId(), "Milo", "Border Collie"),
Dog(ObjectId(), "Lassie", "Rough Collie"),
)
)
collection.find<Dog>(eq(Dog::breed.name, "Rough Collie")).limit(2).collect { print("${it.name} ") }
collection.find<Dog>(eq(Dog::breed.name, "Rough Collie")).limit(2).skip(2).collect { print("${it.name} ") }
// Trufa Duc LassieUpdate
Modifica la classe Dog perquè un gos pugui tenir un propietari:
data class Dog(
@BsonId
val id: ObjectId,
val name: String,
val breed: String?,
val owner: Owner?
)
data class Owner(
val name: String,
val emai: String?
)Si tenim aquest conjunt de documents a la base de dades:
collection.insertMany(
listOf(
Dog(ObjectId(), "Trufa", "Rough Collie", Owner(name = "David", email = "david@gmail.com")),
Dog(ObjectId(), "Ketzu", "Shiba Inu", Owner(name = "Laura", null)),
Dog(ObjectId(), "Marilyn", null, Owner(name = "Roser", "roser@gmail.com")),
Dog(ObjectId(), "Duc", "Rough Collie", null),
Dog(ObjectId(), "Milo", "Border Collie", null),
Dog(ObjectId(), "Lassie", "Rough Collie", Owner(name = "Miquel", null)),
)
)I has trobat un propietari per a “Duc”, pots actualitzar el document corresponent amb updateOne.
collection.updateOne(
filter = eq(Dog::name.name, "Duc"),
update = set(Dog::owner.name, Owner(name = "David", "david@gmail.com"))
).also { println(it) }
collection.find<Dog>(eq(Dog::name.name,"Duc")).first().also { print(it) }La classe Updates proporciona mètodes estàtics per a tots els operadors de consulta de MongoDB.
import com.mongodb.client.model.Updates.*El resultat és el que esperem:
Successfully connected to MongoDB
AcknowledgedUpdateResult{matchedCount=1, modifiedCount=1, upsertedId=null}
Dog(id=67858d99749e816534cc68db, name=Duc, breed=Rough Collie, owner=Owner(name=David, email=david@gmail.com)Ara “Trufa” i “Duc” són del mateix propietari.
Imagina’t que “David” ha de canviar el seu email.
Per a aquests casos hi ha l’operador updateMany.
Primer crea un filtre que seleccioni tots els gossos de “David” i verifica que funciona:
collection.find<Dog>(eq("owner.name","David")).collect{ print("${it.name} ")}I ja pots actualitzar les dades:
collection.updateMany(
filter = eq("owner.name", "David"),
update = set("owner.email", "david@xtec.dev")
)
collection.find<Dog>(eq("owner.name", "David")).collect { println(it) }Delete
Per esborrar un sol document pots utilitzar findOneAndDelete en lloc de deleteOne, amb l’avantatge addicional que també retorna com a sortida el document suprimit.
collection.findOneAndDelete(eq(Dog::name.name,"Milo")).also { print(it) }Per eliminar diversos documents, pots utilitzar deleteMany:
collection.deleteMany(eq(Dog::breed.name, "Rough Collie"))
collection.find<Dog>().collect { print("${it.name} ")} // Ketzu Marilyn TODO
- https://phauer.com/2018/kotlin-mongodb-perfect-match/
- https://www.mongodb.com/resources/languages/kotlin
- https://dev.to/codewithmohit/getting-started-guide-for-kotlin-multiplatform-mobile-kmm-with-flexible-sync-342o
- MongoDB Kotlin Drivers
- MongoDB Kotlin Driver
- https://www.mongodb.com/docs/drivers/kotlin/coroutine/current/fundamentals/data-formats/document-data-format-extended-json/