目次
1. トランザクションと TransactionTemplate
2. トランザクションとMongoTransactionManager
4. トランザクションと TransactionalOperator
5. トランザクションと ReactiveMongoTransactionManager
バージョン 4 以降、MongoDB はトランザクションをサポートします。トランザクションはセッションに基づいて構築されるため、アクティブな ClientSession が必要です。
アプリケーション コンテキストで MongoTransactionManager を指定しない限り、トランザクション サポートは無効になります。setSessionSynchronization(ALWAYS) を使用すると、進行中の非ネイティブ MongoDB トランザクションに参加できます。
トランザクションをプログラムで完全に制御するには、MongoOperations でセッション コールバックを使用するとよいでしょう。
次の例は、SessionCallback 内のプログラムによるトランザクション制御を示しています。
例 124. 手続き型トランザクション
ClientSession session = client.startSession(options);
template.withSession(session)
.execute(action -> {
session.startTransaction();
try {
Step step = // ...;
action.insert(step);
process(step);
action.update(Step.class).apply(Update.set("state", // ...
session.commitTransaction();
} catch (RuntimeException e) {
session.abortTransaction();
}
}, ClientSession::close)
新しい ClientSession を取得します。
ビジネスを始めましょう。
すべてが期待どおりに動作する場合は、変更をコミットします。
問題が発生したため、すべてをロールバックします。
完了したら、セッションを閉じることを忘れないでください。
前述の例では、コールバックでセッション スコープの MongoOperations インスタンスを使用して、セッションが各サーバー呼び出しに確実に渡されるようにしながら、トランザクション動作を完全に制御できます。このアプローチに伴うオーバーヘッドの一部を回避するには、TransactionTemplate を使用して、手動トランザクション フローからノイズの一部を除去します。
1. トランザクションと TransactionTemplate
Spring Data MongoDB トランザクションは TransactionTemplate をサポートします。次の例は、TransactionTemplate を作成して使用する方法を示しています。
例 125. トランザクションと TransactionTemplate
template.setSessionSynchronization(ALWAYS);
// ...
TransactionTemplate txTemplate = new TransactionTemplate(anyTxManager);
txTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
Step step = // ...;
template.insert(step);
process(step);
template.update(Step.class).apply(Update.set("state", // ...
};
});
テンプレート API 設定でトランザクション同期を有効にします。
提供された PlatformTransactionManager を使用して TransactionTemplate を作成します。
コールバックには、ClientSession とトランザクションが登録されています。
実行時に MongoTemplate の状態を変更すると (前のリストの項目 1 で起こる可能性があると思われるかもしれません)、スレッドと可視性の問題が発生する可能性があります。
2. トランザクションとMongoTransactionManager
MongoTransactionManager は、よく知られている Spring トランザクション サポートへのゲートウェイです。これにより、アプリケーションは Spring のトランザクション管理機能を使用できるようになります。MongoTransactionManager は、ClientSession をスレッドにバインドします。MongoTemplate はセッションを検出し、これらのトランザクション関連のリソースに応じて動作します。MongoTemplate は、他の進行中のトランザクションに参加することもできます。次の例は、MongoTransactionManager を使用してトランザクションを作成および使用する方法を示しています。
例 126. トランザクションと MongoTransactionManager
@Configuration
static class Config extends AbstractMongoClientConfiguration {
@Bean
MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
// ...
}
@Component
public class StateService {
@Transactional
void someBusinessFunction(Step step) {
template.insert(step);
process(step);
template.update(Step.class).apply(Update.set("state", // ...
};
});
MongoTransactionManager をアプリケーション コンテキストに登録します。
メソッドをトランザクションとしてマークします。
@Transactional(readOnly = true) は、MongoTransactionManager に対して、発信リクエストに ClientSession を追加してトランザクションを開始するようにアドバイスします。
3. リアクティブなトランザクション
Reactive ClientSession のサポートと同様に、ReactiveMongoTemplate は、操作の結果に基づいて操作をコミットまたは停止することを心配することなく、トランザクション内で操作するための特殊なメソッドを提供します。
アプリケーション コンテキストで ReactiveMongoTransactionManager を指定しない限り、トランザクション サポートは DISABLED (無効) になります。setSessionSynchronization(ALWAYS) を使用すると、進行中の非ネイティブ MongoDB トランザクションに参加できます。
通常の MongoDB リアクティブ ドライバー API を使用すると、トランザクション プロセスでの削除は次のようになります。
例 127. ネイティブドライバーのサポート
Mono<DeleteResult> result = Mono
.from(client.startSession())
.flatMap(session -> {
session.startTransaction();
return Mono.from(collection.deleteMany(session, ...))
.onErrorResume(e -> Mono.from(session.abortTransaction()).then(Mono.error(e)))
.flatMap(val -> Mono.from(session.commitTransaction()).then(Mono.just(val)))
.doFinally(signal -> session.close());
});
まず、明らかにセッションを開始する必要があります。
ClientSession を取得したら、トランザクションを開始します。
ClientSession を操作に渡すことにより、トランザクション内で操作します。
操作が異常終了した場合は、トランザクションを停止してエラーを保持する必要があります。
もちろん、成功した場合に変更をコミットすることも可能です。操作の結果はまだ保持されています。
最後に、セッションが閉じられていることを確認する必要があります。
上記の操作の原因は、commitTransaction() または abortTransaction() を介してポストされたトランザクション結果ではなく、メイン フローの DeleteResult を保持することです。これにより、セットアップがかなり複雑になります。
4. トランザクションと TransactionalOperator
Spring Data MongoDB トランザクションは TransactionalOperator をサポートします。次の例は、TransactionalOperator を作成して使用する方法を示しています。
例 128. トランザクションと TransactionalOperator
template.setSessionSynchronization(ALWAYS);
// ...
TransactionalOperator rxtx = TransactionalOperator.create(anyTxManager,
new DefaultTransactionDefinition());
Step step = // ...;
template.insert(step);
Mono<Void> process(step)
.then(template.update(Step.class).apply(Update.set("state", …))
.as(rxtx::transactional)
.then();
トランザクションに参加するためにトランザクション同期を有効にします。
提供された ReactiveTransactionManager を使用して TransactionalOperator を作成します。
TransactionalOperator.transactional(… ) は、すべての上流操作のトランザクション管理を提供します。
5. トランザクションと ReactiveMongoTransactionManager
ReactiveMongoTransactionManager は、よく知られた Spring トランザクション サポートへのゲートウェイです。これにより、アプリケーションは Spring のマネージド トランザクション機能を利用できるようになります。ReactiveMongoTransactionManager は、ClientSession をサブスクライバ Context にバインドします。ReactiveMongoTemplate はセッションを検出し、これらのトランザクション関連のリソースに応じて動作します。ReactiveMongoTemplate は、他の進行中のトランザクションに参加することもできます。次の例は、ReactiveMongoTransactionManager を使用してトランザクションを作成および使用する方法を示しています。
例 129. トランザクションと ReactiveMongoTransactionManager
@Configuration
public class Config extends AbstractReactiveMongoConfiguration {
@Bean
ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory factory) {
return new ReactiveMongoTransactionManager(factory);
}
// ...
}
@Service
public class StateService {
@Transactional
Mono<UpdateResult> someBusinessFunction(Step step) {
return template.insert(step)
.then(process(step))
.then(template.update(Step.class).apply(Update.set("state", …));
};
});
ReactiveMongoTransactionManager をアプリケーション コンテキストに登録します。
メソッドをトランザクションとしてマークします。
@Transactional(readOnly = true) は、ReactiveMongoTransactionManager に対して、発信リクエストに ClientSession を追加してトランザクションを開始するようにアドバイスします。
6. トランザクション内の特別な動作
トランザクション内では、MongoDB サーバーの動作は少し異なります。
接続設定
MongoDB ドライバーには、ドライバーを自動検出モードにする専用のレプリカ セット名構成オプションが用意されています。このオプションは、プライマリ レプリカ セット ノードを識別し、トランザクション内でコマンドをルーティングするのに役立ちます。
必ず、replicaSet を MongoDB URI に追加してください。詳細については、「接続文字列オプション」を参照してください。
収集操作
MongoDB は、トランザクション内でのコレクションの作成などのコレクション操作をサポートしていません。これは、最初の使用時に行われる即時コレクションの作成にも影響します。したがって、必要な構造がすべて適切に設置されていることを確認してください。
一時的なエラー
MongoDB は、トランザクション操作中に発生するエラーに特別なタグを追加できます。これらのラベルは一時的なエラーを示している可能性があり、操作を再試行すると解消される可能性があります。これらの目的のために、Spring Retry を強くお勧めします。ただし、MongoDB リファレンス マニュアルで説明されているように、MongoTransactionManager#doCommit(MongoTransactionObject) をオーバーライドして、コミット操作を再試行する動作を実装できます。
カウント
MongoDB のカウント操作はコレクション統計に基づいており、トランザクションの実際の状況を反映していない可能性があります。マルチドキュメント トランザクション内で count コマンドが発行されると、サーバーはエラー 50851で応答します。MongoTemplate がアクティブなトランザクションを検出すると、公開されているすべての count() メソッドが変換され、$match 演算子と $count 演算子を使用して集計フレームワークに委任され、照合順序などのクエリ設定が維持されます。
geo コマンドを集計カウント ヘルパーとともに使用する場合は、いくつかの制限があります。次の演算子は使用できないため、別の演算子に置き換える必要があります。
- $where → $expr
- $near → $center のある $geoWithin
- $nearSphere → $centerSphere を使用した $geoWithin
Criteria.near(…) および Criteria.nearSphere(…) を使用するクエリは、それぞれ Criteria.within(…) および Criteria.withinSphere(…) として書き直す必要があります。同じことが、リポジトリ クエリ メソッドの Near クエリ キーワードにも当てはまります。これは、inside に変更する必要があります。詳細については、MongoDB JIRA チケット DRIVERS-518 も参照してください。
以下のスニペットは、セッション バインディング クロージャ内での count の使用法を示しています。
session.startTransaction();
template.withSession(session)
.execute(action -> {
action.count(query(where("state").is("active")), Step.class)
...
上記のスニペットは次のコマンドに具体化されます。
db.collection.aggregate(
[
{ $match: { state: "active" } },
{ $count: "totalEntityCount" }
]
)
その代わり。
db.collection.find( { state: "active" } ).count()