Overview
En esta guía, puede aprender a realizar operaciones compuestas con el controlador Java de MongoDB.
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.
Cómo utilizar operaciones compuestas
Esta sección muestra cómo utilizar cada operación compuesta con el controlador Java de MongoDB.
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"}
El código completo de los siguientes ejemplos está disponible en Github aquí.
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 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.
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 FindOneAndUpdateOptions para especificar las siguientes opciones:
Excluir el campo
_iddel documento encontrado con una proyección.Especifique una operación upsert, que inserta el documento especificado por el filtro de consulta si ningún documento coincide con la consulta.
// <MongoCollection set up code here> // Creates a projection to exclude the "_id" field from the retrieved documents Bson projection = Projections.excludeId(); // Creates a filter to match documents with a "color" value of "green" Bson filter = Filters.eq("color", "green"); // Creates an update document to set the value of "food" to "pizza" Bson update = Updates.set("food", "pizza"); // Defines options that specify projected fields and permit upserts FindOneAndUpdateOptions options = new FindOneAndUpdateOptions() .projection(projection) .upsert(true); // Updates the first matching document with the content of the update document, applying the specified options Document result = collection.findOneAndUpdate(filter, update, options); // Prints the matched document in its state before the operation System.out.println(result.toJson());
La salida del código anterior se parece a la siguiente:
{"food": "pizza", "color": "green"}
Para obtener más información sobre la clase Projections, consulte nuestra
Guía sobre el constructor de proyecciones.
Para obtener más información sobre la operación upsert, consulte nuestra guía sobre upserts.
Para obtener más información sobre los métodos y clases mencionados en esta sección, consulte la siguiente documentación de 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 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.
// <MongoCollection set up code here> // Creates instructions to replace the matching document with a new document Bson filter = Filters.eq("color", "green"); Document replace = new Document("music", "classical").append("color", "green"); // Defines options specifying that the operation should return a document in its post-operation state FindOneAndReplaceOptions options = new FindOneAndReplaceOptions(). returnDocument(ReturnDocument.AFTER); // Atomically finds and replaces the matching document and prints the replacement document Document result = collection.findOneAndReplace(filter, replace, options); System.out.println(result.toJson());
La salida del código anterior se parece a la siguiente:
{"_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:
Buscar y eliminar
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.
// <MongoCollection set up code here> Bson sort = Sorts.descending("_id"); // Creates an empty filter to match all documents in the collection Bson filter = Filters.empty(); // Defines options that specify a descending sort on the "_id" field FindOneAndDeleteOptions options = new FindOneAndDeleteOptions(). sort(sort); // Deletes the document containing the highest "_id" value and prints the deleted document Document result = collection.findOneAndDelete(filter, options); System.out.println(result.toJson());
La salida del código anterior se parece a la siguiente:
{"_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:
Cómo evitar una condición de carrera
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 gestionamos un hotel con una habitación y que tenemos un pequeño programa en Java para ayudarnos a gestionar el check-out de esta habitación a un cliente.
El siguiente documento en MongoDB representa la sala:
{"_id": 1, "guest": null, "room": "Blue Room", "reserved": false}
El código completo de este ejemplo está disponible en Github aquí.
Ejemplo con condición de carrera
Supongamos que nuestra aplicación usa este método bookARoom para registrar la habitación a un huésped:
public void bookARoom() { // Creates a filter to match documents representing available rooms Bson filter = Filters.eq("reserved", false); // Retrieves a document that represents the first available room Document myRoom = this.collection.find(filter).first(); // Prints a message if no documents representing available rooms are found if (myRoom == null){ System.out.println("Sorry, we are booked " + this.guest); return; } String myRoomName = myRoom.getString("room"); // Prints a message that guest that successfully booked the room System.out.println("You got the " + myRoomName + " " + this.guest); // Creates an update document to mark a room as reserved Bson update = Updates.combine(Updates.set("reserved", true), Updates.set("guest", guest)); // Creates a filter that matches the "_id" field of the first available room Bson roomFilter = Filters.eq("_id", myRoom.get("_id", Integer.class)); // Updates the first matching document to mark it as reserved this.collection.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": true}
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:
Encuentra y devuelve una habitación vacía para Jan
Encuentra y devuelve una habitación vacía para Pat.
Actualizar la habitación a reservada para Pat
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.
Ejemplo sin condición de competencia
Utilicemos una operación compuesta para evitar la condición de carrera y dar siempre a nuestros usuarios el mensaje correcto.
public void bookARoom(){ // Creates an update document to mark a room as reserved Bson update = Updates.combine(Updates.set("reserved", true), Updates.set("guest", guest)); // Creates a filter to match a document representing an available room Bson filter = Filters.eq("reserved", false); // Updates the first document that matches the filter to mark it as reserved Document myRoom = this.collection.findOneAndUpdate(filter, update); // Prints a message when there are no available rooms if (myRoom == null){ System.out.println("Sorry, we are booked " + this.guest); return; } // Prints the name of the guest that successfully booked the room String myRoomName = myRoom.getString("room"); System.out.println("You got the " + myRoomName + " " + this.guest); }
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":true}
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:
Encuentra una habitación vacía para Jan y resérvala.
Intenta encontrar una habitación libre para Pat y resérvala. Como no quedan 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.