Overview
En esta guía, puedes aprender cómo realizar operaciones compuestas con el controlador MongoDB Kotlin.
Las operaciones compuestas consisten en una operación de lectura y escritura que se ejecuta como una sola operación atómica. Una operación atómica es una operación que se completa por completo o no se completa en absoluto. Las operaciones atómicas no pueden completarse parcialmente.
Las operaciones atómicas pueden ayudarte a evitar condiciones de carrera en tu código. Una condición de carrera ocurre cuando el comportamiento del código depende del orden de eventos incontrolables.
MongoDB admite las siguientes operaciones compuestas:
Buscar y actualizar un documento
Buscar y reemplazar un documento
Buscar y eliminar un documento
Si necesitas realizar tareas más complejas de manera atómica, como leer y escribir en más de un documento, utiliza transacciones. Las transacciones son una funcionalidad de MongoDB y otras bases de datos que te permite definir una secuencia arbitraria de comandos de base de datos como una operación atómica.
Para obtener más información sobre las operaciones atómicas y la atomicidad, consulta La entrada manual de MongoDB para atomicidad y transacciones.
Para obtener más información sobre transacciones, consulta la entrada del manual de MongoDB sobre transacciones.
Cómo utilizar operaciones compuestas
Esta sección muestra cómo utilizar cada operación compuesta con el controlador MongoDB Kotlin.
Los siguientes ejemplos usan una colección que contiene estos dos documentos de muestra.
{"_id": 1, "food": "donut", "color": "green"} {"_id": 2, "food": "pear", "color": "yellow"}
Estos datos se modelan con la siguiente clase de datos de Kotlin:
data class FoodOrder( val id: Int, val food: String, val color: String )
Nota
¿Antes o después de guardar?
De forma predeterminada, cada operación compuesta devuelve el documento encontrado en el estado anterior a la operación de escritura. Puede recuperar el documento encontrado en el estado posterior a la operación de escritura utilizando la clase de opciones correspondiente a su operación compuesta. Puede ver un ejemplo de esta configuración en el ejemplo de Buscar y Reemplazar a continuación.
Buscar y actualizar
Para encontrar y actualizar un documento, usa el findOneAndUpdate() Método de la clase MongoCollection. El método findOneAndUpdate() devuelve el documento encontrado o null si no hay documentos que coincidan con la consulta.
Ejemplo
El siguiente ejemplo utiliza el método findOneAndUpdate() para encontrar un documento con el campo color establecido en "green" y actualizar el campo food en ese documento a "pizza".
El ejemplo también utiliza una instancia de FindOneAndUpdateOptions para especificar las siguientes opciones:
Especificar una inserción, que inserta el documento especificado por el filtro de query si ningún documento coincide con la query.
Establece un tiempo máximo de ejecución de 5 segundos para esta operación en la instancia de MongoDB. Si la operación toma más tiempo, el método
findOneAndUpdate()generará unaMongoExecutionTimeoutException.
val filter = Filters.eq(FoodOrder::color.name, "green") val update = Updates.set(FoodOrder::food.name, "pizza") val options = FindOneAndUpdateOptions() .upsert(true) /* The result variable contains your document in the state before your update operation is performed or null if the document was inserted due to upsert being true */ val result = collection.findOneAndUpdate(filter, update, options) println(result)
FoodOrder(id=1, food=donut, color=green)
Para obtener más información sobre la clase Projections, consulte
Constructores de Proyecciones.
Para obtener más información sobre la operación de inserción, consulta Insertar o actualizar en una sola operación.
Para obtener más información sobre los métodos y clases mencionados en esta sección, consulte la siguiente documentación de la API:
Buscar y reemplazar
Para buscar y reemplazar un documento, utilice el método findOneAndReplace() de la clase MongoCollection. El método findOneAndReplace() devuelve el documento encontrado o null si no hay documentos que coincidan con su consulta.
Ejemplo
El siguiente ejemplo usa el método findOneAndReplace() para encontrar un documento con el campo color configurado en "green" y reemplazarlo con el siguiente documento:
{"music": "classical", "color": "green"}
En el ejemplo también se utiliza una instancia de FindOneAndReplaceOptions para indicar que el documento devuelto debe estar en el estado después de nuestra operación de reemplazo.
data class Music( val id: Int, val music: String, val color: String ) val filter = Filters.eq(FoodOrder::color.name, "green") val replace = Music(1, "classical", "green") val options = FindOneAndReplaceOptions() .returnDocument(ReturnDocument.AFTER) val result = collection.withDocumentClass<Music>().findOneAndReplace(filter, replace, options) println(result)
Music(id=1, music=classical, color=green)
Para obtener más información sobre los métodos y clases mencionados en esta sección, consulte la siguiente documentación de la API:
Encontrar y borrar
Para buscar y eliminar un documento, utilice el método findOneAndDelete() de la clase MongoCollection. El método findOneAndDelete() devuelve el documento encontrado o null si no hay documentos que coincidan con su consulta.
Ejemplo
El siguiente ejemplo utiliza el método findOneAndDelete() para buscar y eliminar el documento con el valor más grande en el campo _id.
El ejemplo usa una instancia FindOneAndDeleteOptions para especificar un orden descendente en el campo _id.
val sort = Sorts.descending("_id") val filter = Filters.empty() val options = FindOneAndDeleteOptions().sort(sort) val result = collection.findOneAndDelete(filter, options) println(result)
FoodOrder(id=2, food=pear, color=yellow)
Para obtener más información sobre la Sorts clase, consulte nuestra guía sobre el generador de ordenamientos.
Para obtener más información sobre los métodos y clases mencionados en esta sección, consulte la siguiente documentación de la API:
Evitar una condición de competencia
En esta sección exploramos dos ejemplos. El primer ejemplo contiene una condición de competencia, el segundo ejemplo utiliza una operación compuesta para evitar la condición de competencia presente en el primer ejemplo.
Para ambos ejemplos, imaginemos que administramos un hotel con una habitación y que tenemos un pequeño programa Kotlin para ayudarnos a registrar esta habitación para un huésped.
El siguiente documento en MongoDB representa la sala:
{"_id": 1, "guest": null, "room": "Blue Room", "reserved": false}
Estos datos se modelan con la siguiente clase de datos de Kotlin:
data class HotelRoom( val id: Int, val guest: String? = null, val room: String, val reserved: Boolean = false )
Ejemplo con condición de carrera
Supongamos que nuestra aplicación usa este método bookARoomUnsafe para registrar la habitación a un huésped:
suspend fun bookARoomUnsafe(guestName: String) { val filter = Filters.eq("reserved", false) val myRoom = hotelCollection.find(filter).firstOrNull() if (myRoom == null) { println("Sorry, we are booked, $guestName") return } val myRoomName = myRoom.room println("You got the $myRoomName, $guestName") val update = Updates.combine(Updates.set("reserved", true), Updates.set("guest", guestName)) val roomFilter = Filters.eq("_id", myRoom.id) hotelCollection.updateOne(roomFilter, update) }
Imagina que dos huéspedes diferentes, Jan y Pat, intenten reservar la habitación con este método al mismo tiempo.
Jan ve este resultado:
You got the Blue Room, Jan
Y Pat ve este resultado:
You got the Blue Room, Pat
Cuando miramos nuestra base de datos, vemos lo siguiente:
{"_id": 1, "guest": "Jan", "room": "Blue Room", "reserved": false}
Pat no estará feliz. Cuando Pat llegue a nuestro hotel, Jan estará ocupando su habitación. ¿Qué salió mal?
Aquí está la secuencia de acontecimientos que ocurrió desde la perspectiva de nuestra instancia de MongoDB:
Encuentra y devuelve una habitación vacía para enero.
Encuentre y devuelva una habitación vacía para Pat.
Actualiza la habitación a reservada para Pat.
Actualizar la habitación a reservada para enero.
Ten en cuenta que, por un breve momento, Pat reservó la sala, pero como la actualización de Jan fue la última en ejecutarse, nuestro documento tiene a "Jan" como invitado.
Ejemplo sin condición de competencia
Usemos una operación compuesta para evitar la condición de competencia y siempre brindar a nuestros usuarios el mensaje correcto.
suspend fun bookARoomSafe(guestName: String) { val update = Updates.combine( Updates.set(HotelRoom::reserved.name, true), Updates.set(HotelRoom::guest.name, guestName) ) val filter = Filters.eq("reserved", false) val myRoom = hotelCollection.findOneAndUpdate(filter, update) if (myRoom == null) { println("Sorry, we are booked, $guestName") return } val myRoomName = myRoom.room println("You got the $myRoomName, $guestName") }
Imagina que dos huéspedes diferentes, Jan y Pat, intenten reservar la habitación con este método al mismo tiempo.
Jan ve este resultado:
You got the Blue Room, Jan
Y Pat ve este resultado:
Sorry, we are booked, Pat
Cuando miramos nuestra base de datos, vemos lo siguiente:
{"_id": 1, "guest": "Jan", "room": "Blue Room", "reserved": false}
Pat recibió el mensaje correcto. Aunque esté triste por no haber conseguido la reserva, al menos sabe que no debe viajar a nuestro hotel.
Aquí está la secuencia de acontecimientos que ocurrió desde la perspectiva de nuestra instancia de MongoDB:
Busca una sala libre para Jan y resérvala.
Intenta encontrar una habitación vacía para Pat y resérvala.
Cuando no queden habitaciones, devuelve
null.
Importante
bloqueo de escritura
Tu instancia de MongoDB coloca un bloqueo de escritura en el documento que estás modificando durante la duración de tu operación compuesta.
Para obtener información sobre la clase Updates, consulta nuestra guía sobre el Constructor de Actualizaciones.
Para obtener más información sobre la Filters clase, consulte nuestra guía sobre el generador de filtros.
Para obtener más información sobre el método findOneAndUpdate(), consulta la documentación de la API de la clase MongoCollection.