Overview
在本教程中,您可以学习;了解如何运行满足原子性、一致性、隔离性和持久性 (ACID)ACID 一致性保证的多文档事务。
所有MongoDB Server版本都支持单文档事务,并在ACID 事务中对文档执行多次更新时保证ACID compliance。在MongoDB Server v4.0 及更高版本中,您可以跨多个文档、集合和数据库运行符合ACID 的事务。
Tutorial
本教程介绍如何下载对产品股票和采购数据运行多文档事务的 示例应用程序 。本教程使用示例应用程序的transactions 目录中的以下文件:
Transactions.java:访问cart和product集合,然后对这两个集合运行操作以反映啤酒购买情况。该代码在ACID transaction中运行,并且没有ACID 事务来比较这些方法。ChangeStreams.java:返回有关cart和product集合中数据更改的信息。models/Cart.java:表示购物车的 POJO 类,对应于cart集合中的文档。models/Product.java:表示一个商品及其股票的POJO 类,对应于product集合中的一份文档。
验证先决条件。
在开始本教程之前,请确保您已准备好以下组件:
已配置集群的MongoDB Atlas帐户。要学习;了解如何创建集群,请参阅MongoDB入门指南。
Java驾驶员v5.0 或更高版本。
Java 21 或更高版本。
Maven v3.8.7 或更高版本。
启动变更流并配置集合。
要创建cart product和 集合并配置JSON schema,运行ChangeStreams.java文件。导航到 java-quick-start目录并运行以下命令:
mvn compile exec:java \ -Dexec.mainClass="com.mongodb.quickstart.transactions.ChangeStreams" \ -Dmongodb.uri="<connection URI>"
提示
将 <connection URI> 占位符替换为集群的连接 URI。
此文件执行以下操作:
在
test数据库中创建cart集合。在
test数据库中创建product集合。将JSON schema应用于
product集合,以对文档字段设置数据类型和值约束。此模式确保stock字段值保持在0以上,因此任何尝试购买缺货商品的ACID 事务都会引发错误并且不会成功。打开变更流以监控对
test数据库的更改。
通过插入示例数据来启动数据操作。
爱丽丝是一个想要购买啤酒的示例客户。 Transactions.java文件运行数据库操作以反映她的购买情况。要启动此程序,请打开第二个终端窗口并从项目的根目录运行以下代码:
mvn compile exec:java \ -Dexec.mainClass="com.mongodb.quickstart.transactions.Transactions" \ -Dmongodb.uri="<connection URI>"
提示
将 <connection URI> 占位符替换为集群的连接 URI。
该文件会在表示啤酒库存的 product集合中插入一个文档,并将其 stock 值设置为 5。该文档存储以下数据:
{ "_id" : "beer", "price" : NumberDecimal("3"), "stock" : NumberInt(5) }
在没有ACID 事务下运行第一个操作。
插入示例数据后,Transactions.java文件运行第一个更新操作来表示爱丽丝购买了两瓶啤酒。代码调用 aliceWantsTwoBeers() 和 removingBeersFromStock() 方法而不启动ACID 事务。这些方法具有以下定义:
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; } }
aliceWantsTwoBeers() 方法通过将文档插入到表示购买的 cart集合中,将两瓶啤酒添加到爱丽丝的购物车中。然后,removingBeersFromStock() 方法会更新 product集合以反映这些更改并减少 stock 中的 beer 数量。
选择Cart标签页以查看代表爱丽丝购物车的新cart文档,然后选择Product标签页以查看执行以下操作后代表啤酒库存的product文档:
{ "_id" : "Alice", "items" : [ { "price" : NumberDecimal("3"), "productId" : "beer", "quantity" : NumberInt(2) } ] }
{ "_id" : "beer", "price" : NumberDecimal("3"), "stock" : NumberInt(3) }
在多文档事务中运行第二个操作。
首次购买后,Alice 又向购物车添加了两瓶啤酒。通过调用 aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback() 方法并将 MongoClient 作为参数传递,Transactions.java文件使用ACID 事务来运行第二个操作。该方法具有以下定义:
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(); } }
aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback() 方法启动一个会话,然后启动一个ACID 事务。在ACID 事务中,代码调用辅助方法来执行以下操作:
在
cart集合中查找表示爱丽丝购物车的文档将文档的
items.quantity值更新为2更新
product集合中表示啤酒股票的文档以反映此更改
由于它们在多文档ACID transaction中运行,因此 cart 和 product 更新具有原子性。
选择 Cart标签页以查看更新后的表示爱丽丝购物车的 cart文档,然后选择 Product标签页以查看更新后的表示啤酒库存的 product文档:
{ "_id" : "Alice", "items" : [ { "price" : NumberDecimal("3"), "productId" : "beer", "quantity" : NumberInt(4) } ] }
{ "_id" : "beer", "price" : NumberDecimal("3"), "stock" : NumberInt(1) }
在ACID 事务中运行不成功的操作。
最后,爱丽丝尝试在购物车中再添加两瓶啤酒。 Transactions.java文件使用ACID 事务通过再次调用 aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback() 方法来运行第三个操作。
但是,此操作未成功,因为股票中只剩下一种啤酒。 ChangeStreams.java文件中配置的JSON schema可确保 product 集合的 stock 值不能低于 0,因此尝试从其当前值减去 2 会引发错误。 aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback() 方法回滚ACID 事务。
查看变更流输出。
Transactions.java 完成运行后,ChangeStreams.java文件输出如下所示:
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}}
变更流会打印有关集合配置和以下操作的信息:
插入操作,将一个文档添加到表示 beer 的
product集合中。爱丽丝第一次购买了两瓶啤酒,其中包括两项操作:一项是更新
cart集合,一项是更新product集合。这些操作不在ACID 事务中运行。这些操作具有不同的Timestamp值,因为它们不会自动运行。下次爱丽丝购买了两瓶啤酒,这也会更新
cart和product集合。这些操作具有相同的Timestamp值,因为它们在多文档事务中以原子方式运行。
完成本教程后,您将拥有一个可以更新股票管理数据的应用程序。应用程序在有和没有多文档ACID transaction下执行这些更新操作,以比较两个结果。
更多信息
要查看完整的示例应用程序,请参阅 java-quick-startGitHub存储库中的事务文件夹。
要学习;了解有关事务的更多信息,请参阅 事务 指南。