Java和MongoDB 4.0对多文档ACID事务的支持

This article was created in partnership with MongoDB. Thank you for supporting the partners who make SitePoint possible.

本文是与MongoDB合作创建的。 感谢您支持使SitePoint成为可能的合作伙伴。

MongoDB 4.0 adds support for multi-document ACID transactions.

MongoDB 4.0添加了对多文档ACID事务的支持。

But wait… Does that mean MongoDB did not support transactions until now? No, actually MongoDB has always supported transactions in the form of single document transactions. MongoDB 4.0 extends these transactional guarantees across multiple documents, multiple statements, multiple collections, and multiple databases. What good would a database be without any form of transactional data integrity guarantee?

但是,等等……这是否意味着MongoDB直到现在才支持事务? 不,实际上,MongoDB始终以单文档交易的形式支持交易。 MongoDB 4.0将这些事务保证扩展到多个文档,多个语句,多个集合和多个数据库。 没有任何形式的交易数据完整性保证的数据库有什么好处?

Before we dive in this blog post, you can find all the code and try multi-document ACID transactions here.

在我们深入探讨此博客文章之前,您可以在此处找到所有代码并尝试进行多文档ACID事务。

快速开始 (Quick start)

步骤1:启动MongoDB (Step 1: Start MongoDB)

Start a single node MongoDB ReplicaSet in version 4.0.0 minimum on localhost, port 27017.

在localhost端口27017上至少启动4.0.0版本的单节点MongoDB ReplicaSet。

If you use Docker:

如果您使用Docker:

  • You can use start-mongo.sh.

    您可以使用start-mongo.sh

  • When you are done, you can use stop-mongo.sh.

    完成后,可以使用stop-mongo.sh

  • If you want to connect to MongoDB with the Mongo Shell, you can use connect-mongo.sh.

    如果要使用Mongo Shell连接到MongoDB,可以使用connect-mongo.sh

If you prefer to start mongod manually:

如果您希望手动启动mongod:

  • mkdir /tmp/data && mongod --dbpath /tmp/data --replSet rs

    mkdir /tmp/data && mongod --dbpath /tmp/data --replSet rs

  • mongo --eval 'rs.initiate()'

    mongo --eval 'rs.initiate()'

步骤2:启动Java (Step 2: Start Java)

This demo contains two main programs: ChangeStreams.java and Transactions.java.

该演示包含两个主要程序: ChangeStreams.javaTransactions.java

  • Change Steams allow you to be notified of any data changes within a MongoDB collection or database.

    Change Steam使您可以在MongoDB集合或数据库中收到任何数据更改的通知。
  • The Transaction process is the demo itself.

    交易过程就是演示本身。

You need two shells to run them.

您需要两个shell来运行它们。

If you use Docker:

如果您使用Docker:

First shell:

第一壳:

./compile-docker.sh
./change-streams-docker.sh
./compile-docker.sh
./change-streams-docker.sh

Second shell:

第二层:

./transactions-docker.sh

If you do not use Docker, you will need to install Maven 3.5.X and a JDK 10 (or JDK 8 minimum but you will need to update the Java versions in the pom.xml):

如果您不使用Docker,则需要安装Maven 3.5.X和JDK 10(或最低JDK 8,但需要更新pom.xml中的Java版本):

First shell:

第一壳:

./compile.sh
./change-streams.sh
./compile.sh
./change-streams.sh

Second shell:

第二层:

./transactions.sh

Let’s compare our existing single document transactions with MongoDB 4.0’s ACID compliant multi-document transactions and see how we can leverage this new feature with Java.

让我们将现有的单文档事务与MongoDB 4.0的ACID兼容多文档事务进行比较,看看如何在Java中利用这一新功能。

MongoDB 4.0之前的版本 (Prior to MongoDB 4.0)

Even in MongoDB 3.6 and earlier, every write operation is represented as a transaction scoped to the level of an individual document in the storage layer. Because the document model brings together related data that would otherwise be modeled across separate parent-child tables in a tabular schema, MongoDB’s atomic single-document operations provide transaction semantics that meet the data integrity needs of the majority of applications.

即使在MongoDB 3.6和更早版本中,每个写操作都表示为一个事务,范围限定在存储层中单个文档的级别 。 由于文档模型将相关数据汇总在一起,否则将在表格模式中跨单独的父子表对它们进行建模,因此MongoDB的原子单文档操作提供了满足大多数应用程序的数据完整性需求的事务语义。

Every typical write operation modifying multiple documents actually happens in several independent transactions: one for each document.

每个修改多个文档的典型写操作实际上都发生在几个独立的事务中:每个文档一个。

Let’s take an example with a very simple stock management application.

让我们以一个非常简单的库存管理应用程序为例。

First of all, I need a MongoDB Replica Set so please follow the instructions given above to start MongoDB.

首先,我需要一个MongoDB副本集,因此请按照上面给出的说明启动MongoDB。

Now let’s insert the following documents into a product collection:

现在,将以下文档插入product集合中:

MongoDB Enterprise rs:PRIMARY> db.product.insertMany([
    { "_id" : "beer", "price" : NumberDecimal("3.75"), "stock" : NumberInt(5) }, 
    { "_id" : "wine", "price" : NumberDecimal("7.5"), "stock" : NumberInt(3) }
])

Let’s imagine there is a sale on and we want to offer our customers a 20% discount on all our products.

假设有一个销售,我们想为我们的客户提供所有产品20%的折扣。

But before applying this discount, we want to monitor when these operations are happening in MongoDB with Change Streams.

但是在应用此折扣之前,我们想使用Change Streams监视MongoDB中这些操作的发生时间。

Execute the following in Mongo Shell:

在Mongo Shell中执行以下操作:

cursor = db.product.watch([{$match: {operationType: "update"}}]);
while (!cursor.isExhausted()) {
  if (cursor.hasNext()) {
    print(tojson(cursor.next()));
  }
}

Keep this shell on the side, open another Mongo Shell and apply the discount:

将此壳放在一边,打开另一个Mongo Shell并应用折扣:

PRIMARY> db.product.updateMany({}, {$mul: {price:0.8}})
{ "acknowledged" : true, "matchedCount" : 2, "modifiedCount" : 2 }
PRIMARY> db.product.find().pretty()
{
    "_id" : "beer",
    "price" : NumberDecimal("3.00000000000000000"),
    "stock" : 5
}
{
    "_id" : "wine",
    "price" : NumberDecimal("6.0000000000000000"),
    "stock" : 3
}

As you can see, both documents were updated with a single command line but not in a single transaction. Here is what we can see in the Change Stream shell:

如您所见,两个文档都是使用单个命令行更新的,而不是使用单个事务更新的。 这是我们在Change Stream Shell中可以看到的内容:

{
    "_id" : {
        "_data" : "825B4637290000000129295A1004374DC58C611E4C8DA4E5EDE9CF309AC5463C5F6964003C62656572000004"
    },
    "operationType" : "update",
    "clusterTime" : Timestamp(1531328297, 1),
    "ns" : {
        "db" : "test",
        "coll" : "product"
    },
    "documentKey" : {
        "_id" : "beer"
    },
    "updateDescription" : {
        "updatedFields" : {
            "price" : NumberDecimal("3.00000000000000000")
        },
        "removedFields" : [ ]
    }
}
{
    "_id" : {
        "_data" : "825B4637290000000229295A1004374DC58C611E4C8DA4E5EDE9CF309AC5463C5F6964003C77696E65000004"
    },
    "operationType" : "update",
    "clusterTime" : Timestamp(1531328297, 2),
    "ns" : {
        "db" : "test",
        "coll" : "product"
    },
    "documentKey" : {
        "_id" : "wine"
    },
    "updateDescription" : {
        "updatedFields" : {
            "price" : NumberDecimal("6.0000000000000000")
        },
        "removedFields" : [ ]
    }
}

As you can see the cluster times (see the clusterTime key) of the two operations are different: the operations occurred during the same second but the counter of the timestamp has been incremented by one.

如您所见,这两个操作的集群时间(请参阅clusterTime键)是不同的:这些操作在同一秒内发生,但是时间戳记的计数器增加了1。

Thus here each document is updated one at a time and even if this happens really fast, someone else could read the documents while the update is running and see only one of the two products with the discount.

因此,这里每个文档一次更新一次,即使这种更新发生得非常快,其他人也可以在更新运行时阅读这些文档,并且只能看到两个打折产品中的一个。

Most of the time, it is something you can tolerate in your MongoDB database because, as much as possible, we try to embed tightly linked, or related data in the same document. As a result, two updates on the same document happen within a single transaction :

在大多数情况下,您可以在MongoDB数据库中容忍这种情况,因为我们尽可能地将紧密链接或相关数据嵌入到同一文档中。 结果,在同一笔交易中对同一文档进行了两次更新:

PRIMARY> db.product.update({_id: "wine"},{$inc: {stock:1}, $set: {description : "It’s the best wine on Earth"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
PRIMARY> db.product.findOne({_id: "wine"})
{
    "_id" : "wine",
    "price" : NumberDecimal("6.0000000000000000"),
    "stock" : 4,
    "description" : "It’s the best wine on Earth"
}

However, sometimes, you cannot model all of your related data in a single document, and there are a lot of valid reasons for choosing not to embed documents.

但是,有时候,您无法在单个文档中对所有相关数据建模,并且有很多正当理由选择不嵌入文档。

具有多文档ACID事务的MongoDB 4.0 (MongoDB 4.0 with multi-document ACID transactions)

Multi-document ACID transactions in MongoDB are very similar to what you probably already know from traditional relational databases.

MongoDB中的多文档ACID事务与您可能从传统关系数据库中已经知道的非常相似。

MongoDB’s transactions are a conversational set of related operations that must atomically commit or fully rollback with all-or-nothing execution.

MongoDB的事务是一组对话式相关操作,这些操作必须以原子方式提交或完全回滚,而执行全部或全部不执行。

Transactions are used to make sure operations are atomic even across multiple collections or databases. Thus, with snapshot isolation reads, another user can only see all the operations or none of them.

事务用于确保即使在多个集合或数据库之间的操作也是原子的。 因此,使用快照隔离读取,另一个用户只能看到所有操作,也可能看不到任何操作。

Let’s now add a shopping cart to our example.

现在,将购物车添加到示例中。

For this example, 2 collections are required because we are dealing with 2 different business entities: the stock management and the shopping cart each client can create during shopping. The lifecycle of each document in these collections is different.

对于此示例,需要2个集合,因为我们要处理2个不同的业务实体:每个客户在购物期间可以创建的库存管理和购物车。 这些集合中每个文档的生命周期都不同。

A document in the product collection represents an item I’m selling. This contains the current price of the product and the current stock. I created a POJO to represent it : Product.java.

产品集合中的文档代表我要销售的商品。 这包含产品的当前价格和当前库存。 我创建了一个POJO来表示它:Product.java。

{ "_id" : "beer", "price" : NumberDecimal("3"), "stock" : NumberInt(5) }

A shopping cart is created when a client adds its first item in the cart and is removed when the client proceeds to checkout or leaves the website. I created a POJO to represent it : Cart.java.

当客户将其第一项添加到购物车中时,将创建一个购物车,当客户进行结帐或离开网站时,将其删除。 我创建了一个POJO来表示它:Cart.java。

{
    "_id" : "Alice",
    "items" : [
        {
            "price" : NumberDecimal("3"),
            "productId" : "beer",
            "quantity" : NumberInt(2)
        }
    ]
}

The challenge here resides in the fact that I cannot sell more than I possess: if I have 5 beers to sell, I cannot have more than 5 beers distributed across the different client carts.

这里的挑战在于这样一个事实:我不能卖出比我拥有的啤酒多的啤酒:如果我要卖出5杯啤酒,那么在不同的客户手推车上分配的啤酒就不能超过5杯。

To ensure that, I have to make sure that the operation creating or updating the client cart is atomic with the stock update. That’s where the multi-document transaction comes into play. The transaction must fail in the case someone tries to buy something I do not have in my stock. I will add a constraint on the product stock:

为了确保这一点,我必须确保创建或更新客户购物车的操作与库存更新是原子的。 这就是多文档交易起作用的地方。 如果有人试图购买我的库存中没有的东西,则交易必须失败。 我将对产品库存添加一个约束:

db.createCollection("product", {
   validator: {
      $jsonSchema: {
         bsonType: "object",
         required: [ "_id", "price", "stock" ],
         properties: {
            _id: {
               bsonType: "string",
               description: "must be a string and is required"
            },
            price: {
               bsonType: "decimal",
               minimum: 0,
               description: "must be a positive decimal and is required"
            },
            stock: {
               bsonType: "int",
               minimum: 0,
               description: "must be a positive integer and is required"
            }
         }
      }
   }
})

Node that this is already included in the Java code.

Java代码中已包含此节点。

To monitor our example, we are going to use MongoDB Change Streams that were introduced in MongoDB 3.6.

为了监控我们的示例,我们将使用MongoDB 3.6中引入的MongoDB变更流。

In each of the threads of this process called ChangeStreams.java, I am going to monitor one of the 2 collections and print each operation with its associated cluster time.

在这个名为ChangeStreams.java过程的每个线程中,我将监视2个集合之一,并打印每个操作及其关联的群集时间。

// package and imports

public class ChangeStreams {

    private static final Bson filterUpdate = Filters.eq("operationType", "update");
    private static final Bson filterInsertUpdate = Filters.in("operationType", "insert", "update");
    private static final String jsonSchema = "{ $jsonSchema: { bsonType: \"object\", required: [ \"_id\", \"price\", \"stock\" ], properties: { _id: { bsonType: \"string\", description: \"must be a string and is required\" }, price: { bsonType: \"decimal\", minimum: 0, description: \"must be a positive decimal and is required\" }, stock: { bsonType: \"int\", minimum: 0, description: \"must be a positive integer and is required\" } } } } ";

    public static void main(String[] args) {
        MongoDatabase db = initMongoDB(args[0]);
        MongoCollection<Cart> cartCollection = db.getCollection("cart", Cart.class);
        MongoCollection<Product> productCollection = db.getCollection("product", Product.class);
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.submit(() -> watchChangeStream(productCollection, filterUpdate));
        executor.submit(() -> watchChangeStream(cartCollection, filterInsertUpdate));
        ScheduledExecutorService scheduled = Executors.newSingleThreadScheduledExecutor();
        scheduled.scheduleWithFixedDelay(System.out::println, 0, 1, TimeUnit.SECONDS);
    }

    private static void watchChangeStream(MongoCollection<?> collection, Bson filter) {
        System.out.println("Watching " + collection.getNamespace());
        List<Bson> pipeline = Collections.singletonList(Aggregates.match(filter));
        collection.watch(pipeline)
                  .fullDocument(FullDocument.UPDATE_LOOKUP)
                  .forEach((Consumer<ChangeStreamDocument<?>>) doc -> System.out.println(
                          doc.getClusterTime() + " => " + doc.getFullDocument()));
    }

    private static MongoDatabase initMongoDB(String mongodbURI) {
        getLogger("org.mongodb.driver").setLevel(Level.SEVERE);
        CodecRegistry providers = fromProviders(PojoCodecProvider.builder().register("com.mongodb.models").build());
        CodecRegistry codecRegistry = fromRegistries(MongoClient.getDefaultCodecRegistry(), providers);
        MongoClientOptions.Builder options = new MongoClientOptions.Builder().codecRegistry(codecRegistry);
        MongoClientURI uri = new MongoClientURI(mongodbURI, options);
        MongoClient client = new MongoClient(uri);
        MongoDatabase db = client.getDatabase("test");
        db.drop();
        db.createCollection("cart");
        db.createCollection("product", productJsonSchemaValidator());
        return db;
    }

    private static CreateCollectionOptions productJsonSchemaValidator() {
        return new CreateCollectionOptions().validationOptions(
                new ValidationOptions().validationAction(ValidationAction.ERROR).validator(BsonDocument.parse(jsonSchema)));
    }
}

In this example we have 5 beers to sell. Alice wants to buy 2 beers but we are not going to use the new MongoDB 4.0 multi-document transactions for this. We will observe in the change streams two operations : one creating the cart and one updating the stock at 2 different cluster times.

在此示例中,我们有5杯啤酒要出售。 爱丽丝想买2杯啤酒,但是我们不会为此使用新的MongoDB 4.0多文档交易。 我们将在更改流中观察到两种操作:一种创建购物车,另一种在2个不同的群集时间更新库存。

Then Alice adds 2 more beers in her cart and we are going to use a transaction this time. The result in the change stream will be 2 operations happening at the same cluster time.

然后,爱丽丝在她的购物车中又添加了2杯啤酒,这次我们将使用一笔交易。 更改流中的结果将是在同一集群时间发生2次操作。

Finally, she will try to order 2 extra beers but the jsonSchema validator will fail the product update and result in a rollback. We will not see anything in the change stream. Here is the Transaction.java source code:

最后,她将尝试订购2杯额外的啤酒,但jsonSchema验证程序将使产品更新失败并导致回滚。 我们不会在变更流中看到任何东西。 这是Transaction.java源代码:

// package and import

public class Transactions {

    private static MongoClient client;
    private static MongoCollection<Cart> cartCollection;
    private static MongoCollection<Product> productCollection;

    private final BigDecimal BEER_PRICE = BigDecimal.valueOf(3);
    private final String BEER_ID = "beer";

    private final Bson stockUpdate = inc("stock", -2);
    private final Bson filterId = eq("_id", BEER_ID);
    private final Bson filterAlice = eq("_id", "Alice");
    private final Bson matchBeer = elemMatch("items", eq("productId", "beer"));
    private final Bson incrementBeers = inc("items.$.quantity", 2);

    public static void main(String[] args) {
        initMongoDB(args[0]);
        new Transactions().demo();
    }

    private static void initMongoDB(String mongodbURI) {
        getLogger("org.mongodb.driver").setLevel(Level.SEVERE);
        CodecRegistry codecRegistry = fromRegistries(MongoClient.getDefaultCodecRegistry(), fromProviders(
                PojoCodecProvider.builder().register("com.mongodb.models").build()));
        MongoClientOptions.Builder options = new MongoClientOptions.Builder().codecRegistry(codecRegistry);
        MongoClientURI uri = new MongoClientURI(mongodbURI, options);
        client = new MongoClient(uri);
        MongoDatabase db = client.getDatabase("test");
        cartCollection = db.getCollection("cart", Cart.class);
        productCollection = db.getCollection("product", Product.class);
    }

    private void demo() {
        clearCollections();
        insertProductBeer();
        printDatabaseState();
        System.out.println("#########  NO  TRANSACTION #########");
        System.out.println("Alice wants 2 beers.");
        System.out.println("We have to create a cart in the 'cart' collection and update the stock in the 'product' collection.");
        System.out.println("The 2 actions are correlated but can not be executed on the same cluster time.");
        System.out.println("Any error blocking one operation could result in stock error or beer sale we don't own.");
        System.out.println("---------------------------------------------------------------------------");
        aliceWantsTwoBeers();
        sleep();
        removingBeersFromStock();
        System.out.println("####################################\n");
        printDatabaseState();
        sleep();
        System.out.println("\n######### WITH TRANSACTION #########");
        System.out.println("Alice wants 2 extra beers.");
        System.out.println("Now we can update the 2 collections simultaneously.");
        System.out.println("The 2 operations only happen when the transaction is committed.");
        System.out.println("---------------------------------------------------------------------------");
        aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback();
        sleep();
        System.out.println("\n######### WITH TRANSACTION #########");
        System.out.println("Alice wants 2 extra beers.");
        System.out.println("This time we do not have enough beers in stock so the transaction will rollback.");
        System.out.println("---------------------------------------------------------------------------");
        aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback();
        client.close();
    }

    private void aliceWantsTwoExtraBeersInTransactionThenCommitOrRollback() {
        ClientSession session = client.startSession();
        try {
            session.startTransaction(TransactionOptions.builder().writeConcern(WriteConcern.MAJORITY).build());
            aliceWantsTwoExtraBeers(session);
            sleep();
            removingBeerFromStock(session);
            session.commitTransaction();
        } catch (MongoCommandException e) {
            session.abortTransaction();
            System.out.println("####### ROLLBACK TRANSACTION #######");
        } finally {
            session.close();
            System.out.println("####################################\n");
            printDatabaseState();
        }
    }

    private void removingBeersFromStock() {
        System.out.println("Trying to update beer stock : -2 beers.");
        try {
            productCollection.updateOne(filterId, stockUpdate);
        } catch (MongoCommandException e) {
            System.out.println("#####   MongoCommandException  #####");
            System.out.println("##### STOCK CANNOT BE NEGATIVE #####");
            throw e;
        }
    }

    private void removingBeerFromStock(ClientSession session) {
        System.out.println("Trying to update beer stock : -2 beers.");
        try {
            productCollection.updateOne(session, filterId, stockUpdate);
        } catch (MongoCommandException e) {
            System.out.println("#####   MongoCommandException  #####");
            System.out.println("##### STOCK CANNOT BE NEGATIVE #####");
            throw e;
        }
    }

    private void aliceWantsTwoBeers() {
        System.out.println("Alice adds 2 beers in her cart.");
        cartCollection.insertOne(new Cart("Alice", Collections.singletonList(new Cart.Item(BEER_ID, 2, BEER_PRICE))));
    }

    private void aliceWantsTwoExtraBeers(ClientSession session) {
        System.out.println("Updating Alice cart : adding 2 beers.");
        cartCollection.updateOne(session, and(filterAlice, matchBeer), incrementBeers);
    }

    private void insertProductBeer() {
        productCollection.insertOne(new Product(BEER_ID, 5, BEER_PRICE));
    }

    private void clearCollections() {
        productCollection.deleteMany(new BsonDocument());
        cartCollection.deleteMany(new BsonDocument());
    }

    private void printDatabaseState() {
        System.out.println("Database state:");
        printProducts(productCollection.find().into(new ArrayList<>()));
        printCarts(cartCollection.find().into(new ArrayList<>()));
        System.out.println();
    }

    private void printProducts(List<Product> products) {
        products.forEach(System.out::println);
    }

    private void printCarts(List<Cart> carts) {
        if (carts.isEmpty())
            System.out.println("No carts...");
        else
            carts.forEach(System.out::println);
    }

    private void sleep() {
        System.out.println("Sleeping 3 seconds...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.err.println("Oups...");
            e.printStackTrace();
        }
    }
}

Here is the console of the Change Stream :

这是变更流的控制台:

$ ./change-streams.sh 

Watching test.cart
Watching test.product

Timestamp{value=6570052721557110786, seconds=1529709604, inc=2} => Cart{id='Alice', items=[Item{productId=beer, quantity=2, price=3}]}

Timestamp{value=6570052734442012673, seconds=1529709607, inc=1} => Product{id='beer', stock=3, price=3}

Timestamp{value=6570052764506783745, seconds=1529709614, inc=1} => Product{id='beer', stock=1, price=3}
Timestamp{value=6570052764506783745, seconds=1529709614, inc=1} => Cart{id='Alice', items=[Item{productId=beer, quantity=4, price=3}]}

As you can see here, we only get four operations because the two last operations were never committed to the database, and therefore the change stream has nothing to show.

如您在此处看到的,我们仅获得四个操作,因为最后两个操作从未提交给数据库,因此更改流没有任何显示。

You can also note that the two first cluster times are different because we did not use a transaction for the two first operations, and the two last operations share the same cluster time because we used the new MongoDB 4.0 multi-document transaction system, and thus they are atomic.

您还可以注意到,两个第一个集群时间是不同的,因为我们没有为两个第一个操作使用事务,而两个后一个操作共享相同的集群时间,因为我们使用了新的MongoDB 4.0多文档事务系统,因此他们是原子的。

Here is the console of the Transaction java process that sum up everything I said earlier.

这是Transaction Java流程的控制台,总结了我之前说过的所有内容。

$ ./transactions.sh 
Database state:
Product{id='beer', stock=5, price=3}
No carts...

#########  NO  TRANSACTION #########
Alice wants 2 beers.
We have to create a cart in the 'cart' collection and update the stock in the 'product' collection.
The 2 actions are correlated but can not be executed on the same cluster time.
Any error blocking one operation could result in stock error or a sale of beer that we can’t fulfill as we have no stock.
---------------------------------------------------------------------------
Alice adds 2 beers in her cart.
Sleeping 3 seconds...
Trying to update beer stock : -2 beers.
####################################

Database state:
Product{id='beer', stock=3, price=3}
Cart{id='Alice', items=[Item{productId=beer, quantity=2, price=3}]}

Sleeping 3 seconds...

######### WITH TRANSACTION #########
Alice wants 2 extra beers.
Now we can update the 2 collections simultaneously.
The 2 operations only happen when the transaction is committed.
---------------------------------------------------------------------------
Updating Alice cart : adding 2 beers.
Sleeping 3 seconds...
Trying to update beer stock : -2 beers.
####################################

Database state:
Product{id='beer', stock=1, price=3}
Cart{id='Alice', items=[Item{productId=beer, quantity=4, price=3}]}

Sleeping 3 seconds...

######### WITH TRANSACTION #########
Alice wants 2 extra beers.
This time we do not have enough beers in stock so the transaction will rollback.
---------------------------------------------------------------------------
Updating Alice cart : adding 2 beers.
Sleeping 3 seconds...
Trying to update beer stock : -2 beers.
#####   MongoCommandException  #####
##### STOCK CANNOT BE NEGATIVE #####
####### ROLLBACK TRANSACTION #######
####################################

Database state:
Product{id='beer', stock=1, price=3}
Cart{id='Alice', items=[Item{productId=beer, quantity=4, price=3}]}

下一步 (Next Steps)

Thanks for taking the time to read my post – I hope you found it useful and interesting. As a reminder, all the code is available on this Github repository for you to experiment.

感谢您抽出宝贵的时间阅读我的帖子-希望您觉得它有用且有趣。 提醒一下,该Github存储库中提供所有代码,供您进行试验。

If you are looking for a very simple way to get started with MongoDB, you can do that in just 5 clicks on our MongoDB Atlas database service in the cloud.

如果您正在寻找一种非常简单的MongoDB入门方法,那么只需在Cloud中的MongoDB Atlas数据库服务上单击5次即可实现。

Also, multi-document ACID transactions is not the only new feature in MongoDB 4.0, so feel free to take a look at our free course on MongoDB University M040: New Features and Tools in MongoDB 4.0 and our guide to what’s new in MongoDB 4.0 where you can learn more about native type conversions, new visualization and analytics tools, and Kubernetes integration.

此外,多文档ACID事务并不是MongoDB 4.0中的唯一新功能,因此请随时关注我们的MongoDB大学M040免费课程:MongoDB 4.0中的新功能和工具,以及有关MongoDB 4.0中新功能指南。您可以了解有关本机类型转换,新的可视化和分析工具以及Kubernetes集成的更多信息。

翻译自: https://www.sitepoint.com/java-and-mongodb-4-0-support-for-multi-document-acid-transactions/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值