Overview
En esta guía, puedes aprender a realizar operaciones compuestas con el driver de Scala.
Las operaciones compuestas consisten en una operación de lectura y una operación de escritura realizadas 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 pueden ayudarle a evitar condiciones de competencia en su código. Una condición de competencia se produce 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
Encuentra y elimina un documento
Para obtener más información sobre las operaciones atómicas y la atomicidad, consulte Atomicidad y transacción en el manual de MongoDB Server.
Tip
Transacciones
Si necesita realizar tareas más complejas de forma atómica, como leer y guardar en más de un documento, utilice transacciones. Las transacciones le permiten ejecutar una secuencia de comandos de base de datos como una operación atómica. Para obtener más información sobre las transacciones, consulte Transacciones en el manual de MongoDB Server.
Cómo utilizar operaciones compuestas
Esta sección muestra cómo usar el driver Scala para realizar operaciones compuestas.
Los siguientes ejemplos usan una colección que contiene dos documentos de muestra:
{"_id": {"$oid": "1"}, "food": "donut", "color": "green"} {"_id": {"$oid": "2"}, "food": "pear", "color": "yellow"}
Los ejemplos utilizan la siguiente referencia collection para asignar cada documento a una instancia de la clase Document:
val database: MongoDatabase = mongoClient.getDatabase("compound_operations") val collection: MongoCollection[Document] = database.getCollection("example") val hotelCollection: MongoCollection[Document] = database.getCollection("rooms")
Cada operación compuesta devuelve un SingleObservable[Document] que emite el documento coincidente o se completa sin emitir un valor si ningún documento coincide con el filtro de query. Si el filtro de query coincide con varios documentos, el método actúa sobre la primera coincidencia, donde first se refiere al orden natural en el disco a menos que especifique un orden de clasificación en el objeto de opciones correspondiente.
Nota
Devolver documentos después de la operación de guardar
Por defecto, una operación compuesta devuelve el documento coincidente y, a continuación, realiza la operación de guardar en él. Puede dirigir la operación compuesta para que realice primero la operación de guardar pasando un objeto de opciones al método correspondiente. El ejemplo Buscar y reemplazar muestra esta configuración.
Buscar y actualizar
Para buscar y actualizar un documento, utilice el método findOneAndUpdate() de la clase MongoCollection.
Ejemplo
El siguiente ejemplo utiliza el método findOneAndUpdate() para encontrar un documento donde el campo color tiene el valor "green". Luego, actualiza 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 tarda más, el método
findOneAndUpdate()emite unMongoExecutionTimeoutExceptiona través de la función de retornoonError()del observador.
val filter = equal("color", "green") val update = set("food", "pizza") val options = new FindOneAndUpdateOptions() .upsert(true) .maxTime(5, TimeUnit.SECONDS) val observable: SingleObservable[Document] = collection.findOneAndUpdate(filter, update, options) observable.subscribe(new Observer[Document] { override def onNext(doc: Document): Unit = println(doc.toJson()) override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") override def onComplete(): Unit = println("Completed") })
Para obtener más información sobre los métodos y las clases utilizados en esta sección, consulte la siguiente documentación de la API:
Buscar y reemplazar
Para buscar y reemplazar un documento, use el método findOneAndReplace() de la clase MongoCollection.
Ejemplo
El siguiente ejemplo utiliza el método findOneAndReplace() para encontrar un documento donde el campo color tiene el valor "green". Reemplaza el documento coincidente con
{"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 la operación de reemplazo.
val replaceFilter = equal("color", "green") val replacement = Document( "music" -> "classical", "color" -> "green" ) val replaceOptions = new FindOneAndReplaceOptions() .returnDocument(ReturnDocument.AFTER) val replaceObservable: SingleObservable[Document] = collection.findOneAndReplace(replaceFilter, replacement, replaceOptions) replaceObservable.subscribe(new Observer[Document] { override def onNext(doc: Document): Unit = println(doc.toJson()) override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") override def onComplete(): Unit = println("Completed") })
Para obtener más información sobre los métodos y las clases utilizados en esta sección, consulte la siguiente documentación de la API:
Encontrar y borrar
Para buscar y borrar un documento, utilice el método findOneAndDelete() de la clase MongoCollection.
Ejemplo
El siguiente ejemplo utiliza el método findOneAndDelete() para encontrar y borrar el documento con el valor más alto en su campo _id.
El ejemplo usa una instancia FindOneAndDeleteOptions para especificar un orden descendente en el campo _id.
val deleteOptions = new FindOneAndDeleteOptions() .sort(descending("_id")) val deleteObservable: SingleObservable[Document] = collection.findOneAndDelete(empty(), deleteOptions) deleteObservable.subscribe(new Observer[Document] { override def onNext(doc: Document): Unit = println(doc.toJson()) override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") override def onComplete(): Unit = println("Completed") })
Para obtener más información sobre los métodos y las clases utilizados en esta sección, consulte la siguiente documentación de la API:
Evitar una condición de competencia
Al realizar varias operaciones de forma atómica, las operaciones compuestas le ayudan a evitar condiciones de competencia.
Para los ejemplos de esta sección, imagina que tienes un hotel con una habitación. Utilizas un pequeño programa Scala para registrar a los huéspedes en la habitación.
El siguiente documento representa la habitación del hotel:
{"_id": {"$oid": "1"}, "guest": null, "room": "Blue Room", "reserved": false}
Ejemplo con condición de carrera
En el siguiente ejemplo se utiliza la función bookARoomUnsafe() para registrar a un huésped en una habitación. La función realiza las operaciones de búsqueda y actualización por separado.
def bookARoomUnsafe(guestName: String): Unit = { val availableFilter = equal("reserved", false) val findResults = Await.result( hotelCollection.find(availableFilter).first().toFuture(), Duration(10, TimeUnit.SECONDS) ) if (Option(findResults).isEmpty) { println(s"Sorry, we are booked, $guestName") return } val room = findResults val roomName = room.getString("room") println(s"You got the $roomName, $guestName") val reserveUpdate = combine( set("reserved", true), set("guest", guestName) ) val roomFilter = equal("_id", room.get("_id").get) Await.result( hotelCollection.updateOne(roomFilter, reserveUpdate).toFuture(), Duration(10, TimeUnit.SECONDS) ) }
Imagina que dos huéspedes, Jan y Pat, intentan reservar la misma habitación al mismo tiempo.
Jan ve este resultado:
You got the Blue Room, Jan
Pat ve este resultado:
You got the Blue Room, Pat
Cuando se mira la base de datos, se ve el siguiente documento:
{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}
Su aplicación les dijo a Pat y a Jan que habían reservado la habitación, pero la base de datos muestra una reserva solo para Jan. Esta es la secuencia de operaciones que realizó MongoDB:
Encontrar y proporcionar una habitación vacía para Jan
Encuentre y devuelva la habitación vacía para Pat
Actualizar la sala con la reserva de Pat
Actualice la habitación con la reserva de Jan
Aunque Pat reservó la sala por un momento, la operación de actualización de Jan sobrescribió la reserva de Pat porque se ejecutó en último lugar.
Ejemplo sin condición de competencia
En el siguiente ejemplo se utiliza la función bookARoomSafe() para registrar a un huésped en una habitación. La función utiliza findOneAndUpdate() para realizar las operaciones de búsqueda y actualización como una única operación atómica.
def bookARoomSafe(guestName: String): Unit = { val reserveUpdate = combine( set("reserved", true), set("guest", guestName) ) val availableFilter = equal("reserved", false) val room: Document = Await.result( hotelCollection.findOneAndUpdate(availableFilter, reserveUpdate).toFuture(), Duration(10, TimeUnit.SECONDS) ) if (Option(room).isEmpty) { println(s"Sorry, we are booked, $guestName") return } val roomName = room.getString("room") println(s"You got the $roomName, $guestName") }
Imagina que dos huéspedes, Jan y Pat, intentan reservar la misma habitación al mismo tiempo.
Jan ve este resultado:
You got the Blue Room, Jan
Pat ve este resultado:
Sorry, we are booked, Pat
Cuando se mira la base de datos, se ve el siguiente documento:
{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}
Jan reservó la sala y Pat recibió el mensaje correcto de que no había salas disponibles. Esta es la secuencia de operaciones que realizó MongoDB:
Busca una sala libre para Jan y resérvala.
Intenta encontrar y reservar una habitación vacía para Pat
No encuentre habitaciones vacías y complete sin emitir un valor
Debido a que findOneAndUpdate() realiza la búsqueda y la actualización de forma atómica, la operación de Pat no puede leer la sala como disponible después de que se complete la reserva de Jan.
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 más información sobre los métodos y las clases utilizados en esta sección, consulte la siguiente documentación de la API: