跳到主要内容
版本:5.1.1

会话与事务

Doubao 中英对照 Sessions & Transactions

自 3.6 版本起,MongoDB 支持会话概念。使用会话可启用 MongoDB 的因果一致性模型,该模型保证按照尊重操作间因果关系的顺序运行操作。会话分为 ServerSession 实例和 ClientSession 实例。在本节中,当我们提及会话时,指的是 ClientSession。

注意

客户端会话内的操作与会话外的操作之间不相互隔离。

MongoOperations 与 ReactiveMongoOperations 都提供了将 ClientSession 绑定到操作的网关方法。MongoCollection 和 MongoDatabase 使用实现了 MongoDB 集合与数据库接口的会话代理对象,因此您无需在每次调用时都添加会话。这意味着对 MongoCollection#find() 的潜在调用会被委托给 MongoCollection#find(ClientSession)。

备注

诸如 (Reactive)MongoOperations#getCollection 之类的方法返回原生 MongoDB Java 驱动程序的网关对象(例如 MongoCollection),这些对象本身提供了用于 ClientSession 的专用方法。这些方法不是会话代理。当您直接与 MongoCollection 或 MongoDatabase 交互(而非通过 MongoOperations 上的 #execute 回调)时,应在需要处提供 ClientSession。

ClientSession 支持​

以下示例展示了会话的用法:

ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
.causallyConsistent(true)
.build();

ClientSession session = client.startSession(sessionOptions); 1

template.withSession(() -> session)
.execute(action -> {

Query query = query(where("name").is("Durzo Blint"));
Person durzo = action.findOne(query, Person.class); 2

Person azoth = new Person("Kylar Stern");
azoth.setMaster(durzo);

action.insert(azoth); 3

return azoth;
});

session.close() 4
  • Obtain a new session from the server.

  • Use MongoOperation methods as before. The ClientSession gets applied automatically.

  • Make sure to close the ClientSession.

  • Close the session.

注意

When dealing with DBRef instances, especially lazily loaded ones, it is essential to not close the ClientSession before all data is loaded. Otherwise, lazy fetch fails.

MongoDB 事务​

自第 4 版起,MongoDB 支持事务。事务建立在会话之上,因此需要一个活动的 ClientSession。

备注

除非您在应用上下文中指定了 MongoTransactionManager,否则事务支持处于禁用状态。您可以使用 setSessionSynchronization(ALWAYS) 来参与正在进行的非原生 MongoDB 事务。

要获得对事务的完全程序化控制,您可能希望使用 MongoOperations 上的会话回调。

以下示例展示了程序化事务控制:

ClientSession session = client.startSession(options);                   1

template.withSession(session)
.execute(action -> {

session.startTransaction(); 2

try {

Step step = // ...;
action.insert(step);

process(step);

action.update(Step.class).apply(Update.set("state", // ...

session.commitTransaction(); 3

} catch (RuntimeException e) {
session.abortTransaction(); 4
}
}, ClientSession::close) 5
  • Obtain a new ClientSession.

  • Start the transaction.

  • If everything works out as expected, commit the changes.

  • Something broke, so roll back everything.

  • Do not forget to close the session when done.

The preceding example lets you have full control over transactional behavior while using the session scoped MongoOperations instance within the callback to ensure the session is passed on to every server call. To avoid some of the overhead that comes with this approach, you can use a TransactionTemplate to take away some of the noise of manual transaction flow.

使用 TransactionTemplate / TransactionalOperator 的事务​

Spring Data MongoDB 事务同时支持 TransactionTemplate 和 TransactionalOperator。

template.setSessionSynchronization(ALWAYS);                                     1

// ...

TransactionTemplate txTemplate = new TransactionTemplate(anyTxManager); 2

txTemplate.execute(new TransactionCallbackWithoutResult() {

@Override
protected void doInTransactionWithoutResult(TransactionStatus status) { 3

Step step = // ...;
template.insert(step);

process(step);

template.update(Step.class).apply(Update.set("state", // ...
}
});
  • Enable transaction synchronization during Template API configuration.

  • Create the TransactionTemplate using the provided PlatformTransactionManager.

  • Within the callback the ClientSession and transaction are already registered.

警告

Changing state of MongoTemplate during runtime (as you might think would be possible in item 1 of the preceding listing) can cause threading and visibility issues.

使用 MongoTransactionManager 与 ReactiveMongoTransactionManager 的事务​

MongoTransactionManager / ReactiveMongoTransactionManager 是通往熟知的 Spring 事务支持的网关。它让应用能够使用 Spring 的受管事务特性。MongoTransactionManager 将 ClientSession 绑定到线程,而 ReactiveMongoTransactionManager 则为此使用 ReactorContext。MongoTemplate 会检测到该会话,并相应地对与事务关联的这些资源进行操作。MongoTemplate 也可以参与其他正在进行的事务。以下示例展示了如何使用 MongoTransactionManager 创建和使用事务:

@Configuration
static class Config extends AbstractMongoClientConfiguration {

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

@Bean
MongoTemplate mongoTemplate(MongoDatabaseFactory dbFactory) { 1
return new MongoTemplate(dbFactory);
}

// ...
}

@Component
public class StateService {

@Transactional
void someBusinessFunction(Step step) { 2

template.insert(step);

process(step);

template.update(Step.class).apply(Update.set("state", // ...
};
});
  • Register MongoTransactionManager in the application context. Also, make sure to use the same MongoDatabaseFactory when creating MongoTemplate to participate in transactions in the scope of the same MongoDatabaseFactory.

  • Mark methods as transactional.

备注

@Transactional(readOnly = true) advises MongoTransactionManager to also start a transaction that adds the ClientSession to outgoing requests.

控制 MongoDB 专属事务选项​

事务性服务方法可能需要特定的事务选项才能运行事务。Spring Data MongoDB 的事务管理器支持对事务标签求值,例如 @Transactional(label = { "mongo:readConcern=available" })。

默认情况下,使用 mongo: 前缀的标签命名空间由默认配置的 MongoTransactionOptionsResolver 求值。事务标签由 TransactionAttribute 提供,并可通过 TransactionTemplate 和 TransactionalOperator 供程序化事务控制使用。由于 @Transactional(label = …) 具有声明式特性,它提供了一个良好的起点,同时也可作为文档使用。

目前支持以下选项:

最大提交时间

控制 commitTransaction 操作在服务端的最大执行时间。该值的格式对应于 Duration.parse(…) 所用的 ISO-8601 持续时间格式。

用法:mongo:maxCommitTime=PT1S

读关注

设置事务的读关注。

用法:mongo:readConcern=LOCAL|MAJORITY|LINEARIZABLE|SNAPSHOT|AVAILABLE

读取偏好

设置事务的读取偏好。

用法:mongo:readPreference=PRIMARY|SECONDARY|SECONDARY_PREFERRED|PRIMARY_PREFERRED|NEAREST

写关注

设置事务的写关注。

用法:mongo:writeConcern=ACKNOWLEDGED|W1|W2|W3|UNACKNOWLEDGED|JOURNALED|MAJORITY

备注

加入外部事务的嵌套事务不会影响初始事务选项,因为事务已经启动。事务选项仅在启动新事务时应用。

事务内部的特殊行为​

在事务内部,MongoDB 服务端的行为略有不同。

连接设置

MongoDB 驱动程序提供了一个专用的副本集名称配置选项,可将驱动程序切换为自动检测模式。该选项有助于在事务期间识别主副本集节点并进行命令路由。

备注

请务必将 replicaSet 添加到 MongoDB URI 中。更多详情请参阅连接字符串选项。

集合操作

MongoDB 不支持在事务内进行集合操作(例如创建集合)。这也会影响首次使用时动态进行的集合创建。因此,请确保所有所需结构都已就位。

瞬时错误

MongoDB 会为事务操作期间抛出的错误添加特殊标签。这些标签可能表示瞬时故障,仅通过重试操作即可消除。我们强烈建议为此使用 Spring Retry。尽管如此,您也可以重写 MongoTransactionManager#doCommit(MongoTransactionObject),以实现 MongoDB 参考手册中所述的重试提交操作行为。

计数

MongoDB count 基于集合统计信息运行,而这些统计信息可能无法反映事务内的实际情况。在多文档事务内发出 count 命令时,服务端会返回_错误 50851_。一旦 MongoTemplate 检测到活动事务,所有暴露的 count() 方法都会被转换,并使用 $match 和 $count 操作符委托给聚合框架,同时保留 Query 设置(例如 collation)。

在聚合计数助手内部使用地理命令时存在限制。以下操作符不能使用,必须替换为其他操作符:

  • $where → $expr

  • $near → 带 $center 的 $geoWithin

  • $nearSphere → 带 $centerSphere 的 $geoWithin

使用 Criteria.near(…) 和 Criteria.nearSphere(…) 的查询必须分别重写为 Criteria.within(…) 和 Criteria.withinSphere(…)。仓库查询方法中的 near 查询关键词也同样必须改为 within。另请参阅 MongoDB JIRA 工单 DRIVERS-518 以了解更多参考。

以下片段展示了在会话绑定闭包内的 count 用法:

session.startTransaction();

template.withSession(session)
.execute(ops -> {
return ops.count(query(where("state").is("active")), Step.class)
});

上述片段会具体化为以下命令:

db.collection.aggregate(
[
{ $match: { state: "active" } },
{ $count: "totalEntityCount" }
]
)

而非:

db.collection.find( { state: "active" } ).count()