Overview
En este tutorial, puede aprender a ejecutar transacciones de múltiples documentos que cumplan con las garantías de atomicidad, consistencia, aislamiento y durabilidad (ACID).
Todas las versiones de MongoDB Server admiten transacciones de un solo documento y garantizan la conformidad con ACID al realizar múltiples actualizaciones a un documento en una transacción. En MongoDB Server v4.0 y versiones posteriores, puede ejecutar transacciones compatibles con ACID en múltiples documentos, colecciones y bases de datos.
Tutorial
Este tutorial muestra cómo descargar un aplicación de ejemplo que ejecuta transacciones multidocumento sobre el stock de productos y los datos de compras. El tutorial utiliza los siguientes archivos en la aplicación de ejemplo. transactions
directorio:
Transactions.javaAccede a las coleccionescartyproducty, a continuación, ejecuta operaciones en ambas colecciones para reflejar las compras de cerveza. El código opera dentro de una transacción ACID y sin ella para comparar los enfoques.ChangeStreams.java:Devuelve información sobre los cambios de datos en las coleccionescartyproduct.models/Cart.java: Clase POJO que representa un carrito de compras, correspondiente a un documento de lacartcolección.models/Product.java:Clase POJO que representa un artículo y su stock, correspondiente a un documento de la colecciónproduct.
Descargue la aplicación de muestra.
Clone la aplicación de muestra del repositorio GitHub de MongoDB Developer ejecutando el siguiente comando en su terminal:
git clone git@github.com:mongodb-developer/java-quick-start.git
Este repositorio contiene una carpeta transactions que almacena los archivos para este tutorial.
Inicie un flujo de cambios y configure colecciones.
Para crear las cart product colecciones y y configurar un esquema JSON, ejecute el archivo ChangeStreams.java. Vaya al java-quick-start directorio y ejecute el siguiente comando:
mvn compile exec:java \ -Dexec.mainClass="com.mongodb.quickstart.transactions.ChangeStreams" \ -Dmongodb.uri="<connection URI>"
Tip
Reemplace el marcador de posición <connection URI> con la URI de conexión de su clúster.
Este archivo realiza las siguientes acciones:
Crea la colección
carten la base de datostest.Crea la colección
producten la base de datostest.Aplica un esquema JSON a la colección
productque establece restricciones de tipo y valor de datos en los campos del documento. Este esquema garantiza que el valor del campostockse mantenga por encima de0, de modo que cualquier transacción que intente comprar un artículo agotado genere un error y no se complete.Abre un flujo de cambios para supervisar los cambios en la base de datos
test.
Inicie las operaciones de datos insertando datos de muestra.
Alice es una clienta de ejemplo que quiere comprar cerveza. El archivo Transactions.java ejecuta operaciones de base de datos para reflejar sus compras. Para iniciar este programa, abra una segunda ventana de terminal y ejecute el siguiente código desde el directorio raíz del proyecto:
mvn compile exec:java \ -Dexec.mainClass="com.mongodb.quickstart.transactions.Transactions" \ -Dmongodb.uri="<connection URI>"
Tip
Reemplace el marcador de posición <connection URI> con la URI de conexión de su clúster.
El archivo inserta un documento en la colección product que representa el inventario de cerveza y establece su valor stock en 5. Este documento almacena los siguientes datos:
{ "_id" : "beer", "price" : NumberDecimal("3"), "stock" : NumberInt(5) }
Ejecute las primeras operaciones sin una transacción.
Tras insertar datos de muestra, el archivo Transactions.java ejecuta la primera operación de actualización para representar la compra de dos cervezas por parte de Alice. El código invoca los métodos aliceWantsTwoBeers() y removingBeersFromStock() sin iniciar una transacción. Estos métodos tienen las siguientes definiciones:
private static void aliceWantsTwoBeers() { System.out.println("Alice adds 2 beers in her cart."); cartCollection.insertOne(new Cart("Alice", List.of(new Cart.Item(BEER_ID, 2, BEER_PRICE)))); }
private static void removingBeersFromStock() { System.out.println("Trying to update beer stock : -2 beers."); try { productCollection.updateOne(filterId, decrementTwoBeers); } catch (MongoException e) { System.out.println("######## MongoException ########"); System.out.println("##### STOCK CANNOT BE NEGATIVE #####"); throw e; } }
El método aliceWantsTwoBeers() añade dos cervezas al carrito de compras de Alice insertando un documento en la colección cart que representa la compra. Luego, el método removingBeersFromStock() actualiza la colección product para reflejar los cambios y reducir el número de cervezas en stock.
Seleccione el Cart pestaña para ver el nuevo documento cart que representa el carrito de compras de Alice y seleccione la pestaña Product para ver el documento product que representa el inventario de cerveza después de las operaciones:
{ "_id" : "Alice", "items" : [ { "price" : NumberDecimal("3"), "productId" : "beer", "quantity" : NumberInt(2) } ] }
{ "_id" : "beer", "price" : NumberDecimal("3"), "stock" : NumberInt(3) }
Ejecutar una segunda operación dentro de una transacción de múltiples documentos.
Tras la compra inicial, Alice añade dos cervezas más a su carrito. El archivo Transactions.java utiliza una transacción para ejecutar esta segunda operación llamando al método aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback() y pasando un MongoClient como argumento. Este método tiene la siguiente definición:
private static void aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback(MongoClient client) { ClientSession session = client.startSession(); try { session.startTransaction(TransactionOptions.builder().writeConcern(WriteConcern.MAJORITY).build()); aliceWantsTwoExtraBeers(session); sleep(); removingBeerFromStock(session); session.commitTransaction(); } catch (MongoException e) { session.abortTransaction(); System.out.println("####### ROLLBACK TRANSACTION #######"); } finally { session.close(); System.out.println("####################################\n"); printDatabaseState(); } }
El método aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback() inicia una sesión y, a continuación, una transacción. Dentro de la transacción, el código invoca métodos auxiliares para realizar las siguientes acciones:
Encuentra el documento en la colección
cartque representa el carrito de AliceActualizar el valor
items.quantitydel documento por2Actualice el documento en la colección
productque representa el stock de cerveza para reflejar el cambio
Dado que se ejecutan dentro de una transacción ACID de múltiples documentos, las actualizaciones cart y product son atómicas.
Seleccione la pestaña Cart para ver el documento cart actualizado que representa el carrito de compras de Alice, y seleccione la pestaña Product para ver el documento product actualizado que representa el inventario de cerveza:
{ "_id" : "Alice", "items" : [ { "price" : NumberDecimal("3"), "productId" : "beer", "quantity" : NumberInt(4) } ] }
{ "_id" : "beer", "price" : NumberDecimal("3"), "stock" : NumberInt(1) }
Ejecutar una operación fallida dentro de una transacción.
Finalmente, Alicia intenta añadir dos cervezas más a su carrito. El archivo Transactions.java utiliza una transacción para ejecutar esta tercera operación llamando de nuevo al método aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback().
Sin embargo, esta operación no se realiza correctamente porque solo queda una cerveza en stock. El esquema JSON configurado en el archivo ChangeStreams.java garantiza que el valor stock de la colección product no pueda ser inferior a 0, por lo que intentar restar 2 de su valor actual genera un error. El método aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback() revierte la transacción.
Revise la salida del flujo de cambios.
Una vez que Transactions.java termina de ejecutarse, la salida del archivo ChangeStreams.java se parece a lo siguiente:
Dropping the 'test' database. Creating the 'cart' collection. Creating the 'product' collection with a JSON Schema. Watching the collections in the DB test... Timestamp{value=7304460075832180737, seconds=1700702141, inc=1} => Document{{_id=beer, price=3, stock=5}} Timestamp{value=7304460075832180738, seconds=1700702141, inc=2} => Document{{_id=Alice, items=[Document{{price=3, productId=beer, quantity=2}}]}} Timestamp{value=7304460080127148033, seconds=1700702142, inc=1} => Document{{_id=beer, price=3, stock=3}} Timestamp{value=7304460088717082625, seconds=1700702144, inc=1} => Document{{_id=Alice, items=[Document{{price=3, productId=beer, quantity=4}}]}} Timestamp{value=7304460088717082625, seconds=1700702144, inc=1} => Document{{_id=beer, price=3, stock=1}}
El flujo de cambios imprime información sobre la configuración de la colección y las siguientes operaciones:
La operación de inserción, que agrega un documento a la colección
productque representa la cerveza.La primera compra de Alice de dos cervezas incluye dos operaciones: una para actualizar la colección
carty otra para actualizar la colecciónproduct. Estas operaciones no se ejecutan dentro de una transacción. Tienen valoresTimestampdiferentes, ya que no se ejecutan de forma automática.La siguiente compra de Alice de dos cervezas también actualiza las colecciones
cartyproduct. Estas operaciones tienen el mismo valorTimestampporque se ejecutan automáticamente en una transacción multidocumento.
Tras completar este tutorial, dispondrá de una aplicación que actualiza los datos de gestión de inventario. La aplicación realiza estas operaciones de actualización con y sin una transacción ACID multidocumento para comparar ambos resultados.
Información Adicional
Para ver la aplicación de ejemplo completa, consulte la carpeta de transacciones en el java-quick-start repositorio de GitHub.
Para obtener más información sobre las transacciones, consulte la guía de Transacciones.