Docs Menu
Docs Home
/ /

Operaciones compuestas

En esta guía, puede aprender a 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 necesita realizar tareas más complejas de forma atómica, como leer y escribir en más de un documento, utilice transacciones. Las transacciones son una función de MongoDB y otras bases de datos que 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, consulte La entrada manual de MongoDB para atomicidad y transacciones.

Para obtener más información sobre las transacciones,consulte la entrada del manual de MongoDB para transacciones.

Esta sección muestra cómo utilizar cada operación compuesta con el controlador MongoDB Kotlin.

Los siguientes ejemplos utilizan 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 Kotlin:

data class FoodOrder(
@BsonId 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.

Para buscar y actualizar un documento, utilice 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.

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 FindOneAndUpdateOptions para especificar las siguientes opciones:

  • Especifique una operación upsert, que inserta el documento especificado por el filtro de consulta si ningún documento coincide con la consulta.

  • Establezca un tiempo máximo de ejecución de 5 segundos para esta operación en la instancia de MongoDB. Si la operación tarda más, el método findOneAndUpdate() generará un error MongoExecutionTimeoutException.

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 upsert, consulte 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 API:

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.

El siguiente ejemplo utiliza el método findOneAndReplace() para buscar un documento con el campo color establecido en "green" y reemplazarlo con el siguiente documento:

{"music": "classical", "color": "green"}

El ejemplo también utiliza una instancia FindOneAndReplaceOptions para especificar que el documento devuelto debe estar en el estado después de nuestra operación de reemplazo.

data class Music(
@BsonId 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 API:

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.

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 API:

En esta sección, exploramos dos ejemplos. El primero contiene una condición de carrera y el segundo utiliza una operación compuesta para evitarla.

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 Kotlin:

data class HotelRoom(
@BsonId val id: Int,
val guest: String? = null,
val room: String,
val reserved: Boolean = false
)

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

Imagine que dos huéspedes distintos, Jan y Pat, intentan 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á contenta. Cuando Pat llegue a nuestro hotel, Jan ocupará su habitación. ¿Qué salió mal?

Aquí está la secuencia de eventos que ocurrieron desde la perspectiva de nuestra instancia de MongoDB:

  1. Encuentra y devuelve una habitación vacía para enero.

  2. Encuentra y devuelve una habitación vacía para Pat.

  3. Actualice la habitación a reservada para Pat.

  4. Actualizar la habitación a reservada para enero.

Tenga en cuenta que por un breve momento Pat había reservado la habitación, pero como la operación de actualización de Jan fue la última en ejecutarse, nuestro documento tiene a "Jan" como invitado.

Utilicemos una operación compuesta para evitar la condición de carrera y dar siempre 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")
}

Imagine que dos huéspedes distintos, Jan y Pat, intentan 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 eventos que ocurrieron desde la perspectiva de nuestra instancia de MongoDB:

  1. Encuentra una habitación vacía para Jan y resérvala.

  2. Intenta encontrar una habitación vacía para Pat y resérvala.

  3. Cuando no queden habitaciones, devuelve null.

Importante

Bloqueo de escritura

Su instancia de MongoDB coloca un bloqueo de escritura en el documento que está modificando mientras dura su operación compuesta.

Para obtener información sobre la Updates clase, consulte nuestra guía sobre el generador 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 findOneAndUpdate() método, consulte la documentación de la API de la clase MongoCollection.

Volver

Operaciones masivas

En esta página