使用 Spring Data 操控 MongoDB 4.0 事务

工程 | Christoph Strobl | 2018 年 6 月 28 日 | ...

随着 MongoDB 4.0 的发布,ACID 事务已进入 Document 存储,强制执行全有或全无的执行并维护数据完整性。因此,让我们直接从查看同步和异步执行模型开始。

在撰写本文时,MongoDB 多文档事务支持单个副本集,并且感觉与您可能从关系数据库中熟悉的那些事务相同。查看驱动程序 API,您会立即感到宾至如归。

try (ClientSession session = client.startSession()) {

    session.startTransaction();

    try {

        collection.insertOne(session, documentOne);
        collection.insertOne(session, documentTwo);

        session.commitTransaction();

    } catch (Exception e) {
        session.abortTransaction();
    }
}

逻辑会话为 MongoDB 的 因果一致性 以及事务奠定了基础,通过帮助协调跨分布式节点的操作。从 client.startSession() 获得的客户端会话应保持短暂,并在不再需要时释放。因此,请确保 close() 它们。

在较低的协议级别,上述代码片段转换为以下一系列命令,您可以在其中清楚地看到每个命令中都存在的会话 (lsid)。startTransaction 标志与第一个命令一起发送,表示事务开始。完成后,事务通过发送 commitTransaction 提交。

{ insert: "col", ordered: true, $db: "db",
  $clusterTime: { … },
  lsid: { id: { $binary: { base64 : "I3M7Nj…", … } } },
  txnNumber: 1,
  startTransaction: true,
  documents: [ { … } ] }

{ insert: "col", ordered: true, $db: "db",
  $clusterTime: { … },
  lsid: { id: { $binary: { base64 : "I3M7Nj…", … } } },
  txnNumber: 1,
  autocommit: false,
  documents: [ { …} ] }

{ commitTransaction: 1,
  $db: "admin",
  $clusterTime: { … },
  lsid: { id: { $binary: { base64 : "I3M7Nj…", … } } },
  txnNumber: 1 }

随着即将发布的 Spring Data Lovelace 版本,MongoDB 模块将附带对同步和异步事务的专用支持。

从同步部分开始,您可能已经熟悉 Spring Framework 的事务支持。因此,MongoTransactionManager 的存在可能并不奇怪。事务管理器本身是当谈到命令式世界中基于注解的事务支持时的入口点。

现在,由于 MongoDB 在早期版本中不支持事务,因此您必须在 ApplicationContext 中显式注册 MongoTransactionManager。当您这样做时,MongoTemplate 开始参与受管理的事务。这是您需要记住的关键点。以下示例显示了如何配置事务管理器

@Configuration
class Config extends AbstractMongoConfiguration {

  @Bean
  MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
    return new MongoTransactionManager(dbFactory);
  }
}


@Service
class DocumentService {

  private final MongoOperations operations;

  DocumentService(MongoOperations operations) {
    this.operations = operations;
  }

  @Transactional
  void insertDocuments() {

    operations.insert(documentOne);
    operations.insert(documentTwo);
  }
}

非常简单,不是吗?嗯,有点。但有一些不明显的缺点。预计 MongoDB 的下一个主要版本将支持 分片集群 环境,并且在您尝试时会出现错误。此外,作为 MongoDB 用户,您可能习惯了它提供的所有便利。其中一些功能在事务中不可用,包括几乎所有元命令、创建集合、索引以及首次使用集合时的隐式集合创建。为避免错误和挫折,请确保预先设置所需的结构。此外,某些命令的行为可能略有不同。例如,count 使用存储的集合统计信息,这些信息在事务中可能不准确。该命令出错,需要使用聚合计数文档。可用的驱动程序已经通过提供利用聚合策略的替代 countDocuments 方法解决了此问题。

考虑到这一点,让我们继续讨论异步用法。

MongoDB ReactiveStreams 驱动程序 提供了对多文档事务的异步入口点。将驱动程序的原生 Publisher 连接到 Reactor 类型,您可以表达事务用法,如下所示

Mono.from(client.startSession()).flatMap(session -> {

  session.startTransaction();

  return Mono.from(collection.insertOne(session, documentOne))
    .then(Mono.from(collection.insertOne(session, documentTwo)))
    .onErrorResume(e -> Mono.from(session.abortTransaction())
      .then(Mono.error(e)))
    .flatMap(val -> Mono.from(session.commitTransaction())
      .then(Mono.just(val)))
    .doFinally(signal -> session.close());
});

我们需要确保事务终止,无论是成功还是回滚。因此,onErrorResume(…) 确保事务在失败时回滚,并确保最终的 flatMap(…) 提交事务,这两者都保留了主流程结果或错误。并且,像往常一样,当您不再需要会话时,请确保关闭它(在 doFinally(…) 块中)。

与同步部分不同,在撰写本文时,没有可用的异步事务管理器可以让您使用 @Transactional 注解方法并让您继续执行正常工作。

相反,您可以通过 ReactiveMongoTemplate.inTransaction(…) 访问事务闭包。它负责所有必需的会话、提交和中止处理,同时维护主流程结果。回调中的处理步骤在 MongoDB 事务内执行,而外部的处理步骤不会影响事务。这意味着闭包外部的处理错误不会导致事务中止,如下面的示例所示。

template.inTransaction().execute(action ->

    // All code in here runs inside the transaction
    action.insert(documentOne).then(action.insert(documentTwo)

  ).flatMap(val -> {
    // An exception here does not affect the transaction
  });

如果您在整个流程中需要访问 ClientSession,它在 Reactor Context 中可用,您可以从 ReactiveMongoContext.getSession() 获取它。

最后一点:如果您能尝试一下并提供您的反馈,我们将不胜感激!因此,请查看 Spring Data 示例,您可以在其中找到一个专门的 项目


如果您想了解更多关于 Spring Data 或 Spring 生态系统的信息,即将在华盛顿特区举行的 SpringOne Platform 大会是最佳时间和地点。查看 会议 并注册!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以助您快速提升。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部