Java与MongoDB 4.0多文档事务新特性体验

2020-05-28 00:00:00 数据库 文档 操作 事务 两个

作者:Maxime Beugnet 

 译者:徐雷( Frank Xu


01

MongoDB4.0多文档事务新特性介绍


MongoDB 4.0增加了对多文档ACID事务的支持。但等等......这是否意味着MongoDB直到现在才支持事务?不,实际上MongoDB已经提供了对单个文档事务的支持。 MongoDB 4.0跨多文档、多语句、多集合和多数据库扩展了事务保证。 如果没有任何形式的事务数据完整性保证,数据库还有什么用呢?


在我们深入阅读这篇博文之前,大家可以在此处找到所有代码并尝试多文档ACID事务新特性。


02

快速入门

步骤1: 启动MongoDB


使用小版本4.0.0,以ReplicaSet模式在localhost本机端口27017上启动MongoDB数据库。


如果使用的是Docker工具:

可以使用start-mongo.sh启动数据库。

完成后,可以使用stop-mongo.sh停止数据库。

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


如果喜欢手动方式启动mongod:

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

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


步骤2: 启动Java程序


Demo包含两个主要程序:

1.ChangeStreams.java

2.Transactions.java


•Change Steams允许收取MongoDB集合或数据库中任何数据更改的通知。

事务过程就是Demo本身。


需要两个shell来运行它们。

如果使用 Docker:

shell:


第二个shell:


如果没有使用Docker,则需要安装Maven 3.5.XJDK 10(或至少JDK 8,但需要更新pom.xml中的Java版本):

shell:


第二个shell:



我们将现有的单文档事务与MongoDB 4.0 ACID多文档事务进行比较,看看如何利用Java来使用这一新特性。


03

MongoDB4.0之前版本

MongoDB 3.6及更早版本中,每个写操作都表示为单个文档级别的事务。 由于文档模型将相关数据汇集在一起,否则这些数据将以表格schema在单独的父子表中建模,因此MongoDB的原子单文档操作提供了满足大多数应用程序的数据完整性需求的事务语义。


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


让我们以一个非常简单的电商库存管理数据库为例。


首先,需要一个MongoDB副本集,请按照上面说明启动MongoDB数据库。


现在让我们将以下文档插入到产品product 集合中:

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


但在使用此折扣之前,我们希望通过Change Streams监控MongoDB中这些操作的发生时间。


Mongo Shell中执行以下命令:


将此shell保留,打开另一个Mongo Shell并应用折扣:


如上所示,两个文档都使用单个命令行进行更新,但不是在一个事务中。 以下是我们在Change Stream shell中可以看到的内容:

如上所示,两个操作的集群时间(clusterTime)不同:操作在同一秒内发生,但时间戳的计数器已增加1


因此,这里文档一次更新一个,即使这种操作非常快,其他人也可以在更新运行时阅读文档,只看到两个产品中的一个有折扣。


大多数情况下,可以在MongoDB数据库中容忍这种情况,因为我们尽可能地尝试在同一文档中嵌入紧密链接或相关数据。 因此,同一文档的两个更新发生在一个事务中:


但是,有时候,无法在单个文档中对所有相关数据进行建模,并且可能还有很多正当理由不去使用嵌入文档方式。


04

MongoDB4.0多文档ACID事务

MongoDB的多文档ACID事务与传统关系数据库中已知的事务非常类似。


MongoDB事务是一组相关操作,必须以全有或全无的形式提交或全部回滚。


事务用于确保在多个集合或数据库中操作是原子性的。 因此,对于快照隔离读取,另一个用户要么看到所有操作或要么看不到操作。


现在让我们在Demo示例中添加购物车。


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


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



当客户在购物车中添加个商品时会创建购物车,并在客户端结帐或离开网站时将其删除。 我创建了一个POJO来代表购物车:Cart.java


这里的挑战在于我不能卖得比库存多:如果我有5瓶啤酒可以卖,不能在购物车上分发超过5瓶啤酒。


为了确保这一点,我必须确保创建或更新客户端购物车的操作与库存更新是原子性的。 这就是多文档事务发挥作用的地方。 如果有人试图购买库存中没有的东西,事务必须失败。 我在产品库存上添加约束:

注意 这些已包含在Java代码中。


为了监控我们的示例,我们将使用MongoDB 3.6中引入的MongoDB Change Streams


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


在这个例子中,我们有5种啤酒可供出售。 Alice希望购买2瓶啤酒,但我们不会为此使用新的MongoDB 4.0多文档事务。


我们将在变更流中观察两个操作:一个创建购物车,另一个在两个不同的集群时间更新库存。


然后Alice在购物车中添加了2瓶啤酒,这次我们使用事务。变更流中的结果将监控在同一群集时间发生的2个操作。


后,她将尝试订购2个额外的啤酒,但jsonSchema验证器将无法通过产品更新并导致事务回滚。 我们不会在变更流中看到任何内容。 这是Transaction.java源代码:


这里是Change Stream的控制台:


正如大家在此处所看到的,我们监控到四个操作信息,因为后两个操作没有提交到数据库,因此变更流没有任何显示。


大家还可以注意到,两个个集群时间是不同的,因为我们没有对两个个操作使用事务,并且后两个操作共享相同的集群时间,因为我们使用了新的MongoDB 4.0多文档事务特性,因此 2个操作是原子性的。


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


05

扩展学习

感谢大家花时间阅读我的文章 - 希望对你有帮助并且充满趣味。 提醒一下,所有代码都可以在这个Github仓库中找到,供大家参考实验学习。


如果您想以非常简单的方式来开始使用MongoDB,只需点击5次点击云中的MongoDB Atlas数据库服务即可。


此外,多文档ACID事务并不是MongoDB 4.0中的新功能,所以请随时查看我们的MongoDB大学M040免费课程:《MongoDB 4.0中的新功能和工具》以及我们的《MongoDB 4.0新增功能指南》,可以了解有关原生类型转换、新的可视化和分析工具以及Kubernetes集成的更多信息。


/

译者简介

/

徐雷( Frank Xu),MongoDB中文社区联席主席,官方《MongoDB实战》第2版译者,《阿里巴巴Java和MongoDB认证》讲师,目前与阿里巴巴专家P9叶翔直播《阿里巴巴MongoDB实战课程》。



往期回顾

热门活动:

福利 | 分享你和MongoDB的故事,获免费海外参会机会

2018年MongoDB北京大会参会指南


技术文章:

MongoDB Aggregate 业务场景实战

MongoDB 集群请求连接被拒绝的分析

完美数据迁移-MongoDB Stream的应用

MongoDB 新功能介绍-Change Streams

MongoDB 4.0 系列之 —事务实现解析(一)

MongoDB 4.0 系列之 —事务实现解析(二)

使用mlaunch和m快速搭建MongoDB测试集群


精彩译文:

为什么MongoDB适合深度学习?

MongoDB Compass聚合管道构建器新特性介绍

相关文章