mongodb4.2版本中增加了mongodb的多文档事务支持,在之前的版本中mongodb支持单文档事务,在单个文档上支持ACID原子性。在 4.2 中,分布式事务和多文档事务都是称为分布式事务。使得分布到集群中不同 sharded 的多文档 ACID 事务变得更加简单。MongoDB 通过二阶段提交的方式,实现在多个 Shard 间发生的修改,要么同时发生,要么都不发生,保证事务的 ACID 特性。除此之外,这个版本还删除事务的 16MB 总大小限制。在4.2 版本中,MongoDB 根据需要创建尽可能多的 oplog 实体(每个最大大小为 16MB),以封装事务中的所有写入操作。而在 MongoDB 4.0 中,MongoDB 为事务中的所有写操作创建单个条目,从而对事务强加 16MB 的总大小限制。
升级mongodb注意事项
对应的mongodb驱动driver版本也需要升级到3.11以上。
在mongodb单机版测试事务不支持,需要在分布式mongodb数据库上测试才可以。
mongodb复制集集群安装
笔者已经将mongodb复制集集群安装步骤总结成了markdown文章,文档地址如下
https://www.zybuluo.com/mdeditor#1622459-full-reader
项目依赖
<dependency><groupId>org.mongodb</groupId><artifactId>mongo-java-driver</artifactId><version>3.11.0</version></dependency>
代码测试清单如下
package io.jopen.db.mongo.transaction;import com.mongodb.*;import com.mongodb.client.*;import com.mongodb.client.MongoClient;import org.bson.Document;import org.junit.Before;import org.junit.Test;/*** 多文档事务测试* https://www.zybuluo.com/mdeditor#1622459-full-reader** @author maxuefeng* @since 2019/10/16*/public class DistributeTransactionTest {private String uri = "mongodb://192.168.74.136:27018,192.168.74.138:27018,192.168.74.139:27018";private ConnectionString connectionString = new ConnectionString(uri);private MongoClient client = null;@Beforepublic void before() {client = MongoClients.create(connectionString);}/*** For a replica set, include the replica set name and a seedlist of the members in the URI string; e.g.* String uri = "mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017/admin?replicaSet=myRepl";* For a sharded cluster, connect to the mongos instances; e.g.* String uri = "mongodb://mongos0.example.com:27017,mongos1.example.com:27017:27017/admin";*/@Testpublic void testTransaction() {client.getDatabase("mydb1").getCollection("foo").withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("abc", 0));client.getDatabase("mydb2").getCollection("bar").withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("xyz", 0));final ClientSession clientSession = client.startSession();TransactionOptions txnOptions = TransactionOptions.builder().readPreference(ReadPreference.primary()).readConcern(ReadConcern.LOCAL).writeConcern(WriteConcern.MAJORITY).build();TransactionBody txnBody = (TransactionBody<String>) () -> {// 创建集合MongoCollection<Document> coll1 = client.getDatabase("mydb1").getCollection("foo");MongoCollection<Document> coll2 = client.getDatabase("mydb2").getCollection("bar");coll1.insertOne(clientSession, new Document("abc", 1));// 第一条数据插入成功System.err.println("data insert success");// 抛出运行时异常double a = 10 0;// 如果上一条数据插入成功 下一条数据插入失败,事务需要回滚coll2.insertOne(clientSession, new Document("xyz", 999));return "Inserted into collections in different databases";};try {clientSession.withTransaction(txnBody, txnOptions);} catch (RuntimeException e) {e.printStackTrace();} finally {clientSession.close();}}}
如上所示 ,如果第一个文档数据插入成功,则会打印data insert success,在下面有一个运行时异常的语句double a = 10 / 0;如果运行出现异常,我们期望的结果是两条语句插入的数据都不可以成功。但在逻辑上来说第一条数据插入语句是执行成功了的。测试结果如下
data insert successjava.lang.ArithmeticException: by zeroat io.jopen.db.mongo.transaction.DistributeTransactionTest.lambda$testTransaction$0(DistributeTransactionTest.java:60)at com.mongodb.client.internal.ClientSessionImpl.withTransaction(ClientSessionImpl.java:194)at io.jopen.db.mongo.transaction.DistributeTransactionTest.testTransaction(DistributeTransactionTest.java:67)
data insert success被打印出来了,从逻辑上来说第一条语句插入成功,但正如我们期望的是它是以事务的形式执行的数据修改操作,所以一条数据都没有插入。




