AI エージェント向け: ドキュメントインデックスは https://www.mongodb.com/ja-jp/docs/llms.txt で利用できます。すべてのページの markdown バージョンは、いずれかの URL パスに .md を追加することで利用できます。
Docs Menu

複合演算子

このガイドでは、 Scalaドライバーを使用して複合操作を実行する方法を学習できます。

複合操作は、1 つのアトミック操作として実行される読み取り操作と書込み操作で構成されます。アトミック操作とは、完全に完了するか、まったく完了しない操作です。

アトミック操作は、コード内の競合状態を回避するのに役立ちます。コードの動作が制御できないイベントの順序によって依存している場合、競合状態が発生します。

MongoDB は次の複合操作をサポートしています。

  • 1 つのドキュメントを検索して更新

  • 1 つのドキュメントを検索して置換

  • 1 つのドキュメントを検索して削除

アトミック操作とアトミック性の詳細については、 MongoDB Serverマニュアルの「 アトミシティとトランザクション 」を参照してください。

Tip

トランザクション

複数のドキュメントの読み取りと書き込みなど、より複雑なタスクをアトミックに実行する必要がある場合は、 トランザクション を使用します。トランザクションを使用すると、データベースコマンドのシーケンスをアトミック操作として実行できます。トランザクションの詳細については、 MongoDB Serverマニュアルの「 トランザクション 」を参照してください。

このセクションでは、 Scalaドライバーを使用して複合操作を実行する方法を説明します。

次の例では、2 つのサンプルドキュメントを含むコレクションを使用します。

{"_id": {"$oid": "1"}, "food": "donut", "color": "green"}
{"_id": {"$oid": "2"}, "food": "pear", "color": "yellow"}

例では、次の collection参照を使用して、各ドキュメントをDocumentクラスのインスタンスにマッピングします。

val database: MongoDatabase = mongoClient.getDatabase("compound_operations")
val collection: MongoCollection[Document] = database.getCollection("example")
val hotelCollection: MongoCollection[Document] = database.getCollection("rooms")

各 複合操作は、一致したドキュメントを発行する SingleObservable[Document] を返します。または、クエリフィルターに一致するドキュメントがない場合は、値を発行せずに完了します。クエリフィルターが複数のドキュメントに一致する場合、 メソッドは最初の一致で動作します。この場合、対応する オプションオブジェクトでソート順序を指定しない限り、first はディスク上の自然な順序を参照します。

注意

書込み操作後にドキュメントを返す

デフォルトでは 、複合操作により一致するドキュメントが返され、そのドキュメントに対して書込み操作が実行されます。オプションオブジェクトを対応するメソッドに渡すことで、複合操作に最初に書込み操作を実行させることができます。 「検索と置換」の例では、この構成を示しています。

1 つのドキュメントを検索して更新するには、MongoCollectionクラスの findOneAndUpdate() メソッドを使用します。

次の例では、findOneAndUpdate() メソッドを使用して、colorフィールドの値が "green" であるドキュメントを検索します。次に、そのドキュメントの foodフィールドを"pizza" にアップデートします。

この例では、 FindOneAndUpdateOptionsインスタンスを使用して次のオプションも指定しています。

  • クエリに一致するドキュメントがない場合に、クエリフィルターで指定されたドキュメントを挿入する アップサート を指定します。

  • MongoDBインスタンスでこの操作に最大実行時間を 5 秒設定します。操作に時間がかかる場合、findOneAndUpdate() メソッドはオブジェクトの onError()コールバックを通じて MongoExecutionTimeoutException を出力します。

val filter = equal("color", "green")
val update = set("food", "pizza")
val options = new FindOneAndUpdateOptions()
.upsert(true)
.maxTime(5, TimeUnit.SECONDS)
val observable: SingleObservable[Document] =
collection.findOneAndUpdate(filter, update, options)
observable.subscribe(new Observer[Document] {
override def onNext(doc: Document): Unit = println(doc.toJson())
override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}")
override def onComplete(): Unit = println("Completed")
})
{"_id": {"$oid": "1"}, "color": "green", "food": "pizza"}
Completed

このセクションで使用されるメソッドとクラスの詳細については、次のAPIドキュメントを参照してください。

1 つのドキュメントを検索して置き換えるには、MongoCollectionクラスの findOneAndReplace() メソッドを使用します。

次の例では、findOneAndReplace() メソッドを使用して、colorフィールドの値が "green" であるドキュメントを検索します。一致するドキュメントを に置き換えます

{"music": "classical", "color": "green"}

この例ではまた、FindOneAndReplaceOptionsインスタンスを使用して、返されるドキュメントが 置換操作後に 状態になることを指定しています。

val replaceFilter = equal("color", "green")
val replacement = Document(
"music" -> "classical",
"color" -> "green"
)
val replaceOptions = new FindOneAndReplaceOptions()
.returnDocument(ReturnDocument.AFTER)
val replaceObservable: SingleObservable[Document] =
collection.findOneAndReplace(replaceFilter, replacement, replaceOptions)
replaceObservable.subscribe(new Observer[Document] {
override def onNext(doc: Document): Unit = println(doc.toJson())
override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}")
override def onComplete(): Unit = println("Completed")
})
{"_id": {"$oid": "1"}, "music": "classical", "color": "green"}
Completed

このセクションで使用されるメソッドとクラスの詳細については、次のAPIドキュメントを参照してください。

1 つのドキュメントを検索して削除するには、MongoCollectionクラスの findOneAndDelete() メソッドを使用します。

次の例では、findOneAndDelete() メソッドを使用して、_idフィールドの 値が最大であるドキュメントを検索して削除します。

この例では、 FindOneAndDeleteOptionsインスタンスを使用して、 _idフィールドで降順の並べ替えを指定しています。

val deleteOptions = new FindOneAndDeleteOptions()
.sort(descending("_id"))
val deleteObservable: SingleObservable[Document] =
collection.findOneAndDelete(empty(), deleteOptions)
deleteObservable.subscribe(new Observer[Document] {
override def onNext(doc: Document): Unit = println(doc.toJson())
override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}")
override def onComplete(): Unit = println("Completed")
})
{"_id": {"$oid": "2"}, "food": "pear", "color": "yellow"}
Completed

このセクションで使用されるメソッドとクラスの詳細については、次のAPIドキュメントを参照してください。

複合操作 は複数の操作をアトミックに実行することで、競合状態を回避するのに役立ちます。

このセクションの例では、1 つの部屋を持つプロパティを実行することを想定してください。小さなScalaプログラムを使用して、訪問者をチェックポイントにチェックインします。

次のドキュメントは、レストランを表しています。

{"_id": {"$oid": "1"}, "guest": null, "room": "Blue Room", "reserved": false}

次の例では、bookARoomUnsafe() 関数を使用してゲストを部屋にチェックインします。この関数は、検索操作と更新操作を個別に実行します。

def bookARoomUnsafe(guestName: String): Unit = {
val availableFilter = equal("reserved", false)
val findResults = Await.result(
hotelCollection.find(availableFilter).first().toFuture(),
Duration(10, TimeUnit.SECONDS)
)
if (Option(findResults).isEmpty) {
println(s"Sorry, we are booked, $guestName")
return
}
val room = findResults
val roomName = room.getString("room")
println(s"You got the $roomName, $guestName")
val reserveUpdate = combine(
set("reserved", true),
set("guest", guestName)
)
val roomFilter = equal("_id", room.get("_id").get)
Await.result(
hotelCollection.updateOne(roomFilter, reserveUpdate).toFuture(),
Duration(10, TimeUnit.SECONDS)
)
}

Jan と Pat の 2 人の利用者が、同時に同じ部屋を予約しようとしているとします。

Jan には次の出力が表示されます。

You got the Blue Room, Jan

Atlas では次の出力が表示されます。

You got the Blue Room, Pat

データベースを確認すると、次のドキュメントが表示されます。

{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}

アプリケーションは、Pat と Jan の両方に問題が発生したことを通知しましたが、データベースには 1 月の予約のみが表示されます。 MongoDB が実行した操作のシーケンスを次に示します。

  1. 1 月の空のスペースを検索して返す

  2. パスカル スペースを検索して返します

  3. パッチの予約で部屋を更新する

  4. 1月の予約で部屋を更新

Atlas は一時的な部屋を予約していましたが、Jan の更新操作は最後に実行されたため、Pat の予約を上書きしました。

次の例では、bookARoomSafe() 関数を使用してゲストを部屋にチェックインします。この関数は findOneAndUpdate() を使用して、検索とアップデートの操作を単一のアトミック操作として実行します。

def bookARoomSafe(guestName: String): Unit = {
val reserveUpdate = combine(
set("reserved", true),
set("guest", guestName)
)
val availableFilter = equal("reserved", false)
val room: Document = Await.result(
hotelCollection.findOneAndUpdate(availableFilter, reserveUpdate).toFuture(),
Duration(10, TimeUnit.SECONDS)
)
if (Option(room).isEmpty) {
println(s"Sorry, we are booked, $guestName")
return
}
val roomName = room.getString("room")
println(s"You got the $roomName, $guestName")
}

Jan と Pat の 2 人の利用者が、同時に同じ部屋を予約しようとしているとします。

Jan には次の出力が表示されます。

You got the Blue Room, Jan

Atlas では次の出力が表示されます。

Sorry, we are booked, Pat

データベースを確認すると、次のドキュメントが表示されます。

{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}

1 月 はこの部屋を予約し、Pat は利用可能な部屋がないという正しいメッセージを受信しました。 MongoDB が実行した操作のシーケンスを次に示します。

  1. 1月用の空のスペースを見つけて予約する

  2. パッチ用の空のスペースを見つけて予約してみてください

  3. 空の場所を見つけられ、値を発行せずに完了します

findOneAndUpdate() は検索と更新をアトミックに実行するため、Jan の予約が完了した後に Atlas App Services の操作では利用可能なリージョンを読み取ることができません。

重要

書込みロック (write lock)

MongoDBインスタンスは、 複合操作の実行中に変更するドキュメントに対して書込みロック (write lock)を配置します。

このセクションで使用されるメソッドとクラスの詳細については、次のAPIドキュメントを参照してください。