Overview
このガイドでは、Scala ドライバーを使用して複合操作を実行する方法を学ぶことができます。
複合操作は、1 つの読み取り操作と 1 つの書き込み操作を 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 は対応するオプションオブジェクトでソート順序を指定しない限り、ディスク上の自然な順序を指します。
注意
書き込み操作後のドキュメントの返回
デフォルトでは、複合操作は一致するドキュメントを返し、その後、そのドキュメントに書き込み操作を実行します。対応するメソッドにオプションオブジェクトを渡すことで、複合操作に書き込み (write) 操作を先に実行させることができます。検索と置換の例では、この構成を示しています。
検索と更新
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") })
このセクションで使用されるメソッドとクラスの詳細については、次の 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") })
このセクションで使用されるメソッドとクラスの詳細については、次の 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") })
このセクションで使用されるメソッドとクラスの詳細については、次の 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
Pat は次の出力を確認します。
You got the Blue Room, Pat
データベースを見ると、次のドキュメントが表示されます。
{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}
アプリケーションは、Pat と Jan の両方に部屋を予約したことを伝えましたが、データベースには Jan の予約のみが表示されています。MongoDB が実行した操作の順序は次のとおりです。
1 月の空のスペースを検索して返す
パッチの空きの部屋を検索して返します。
Pat の予約で部屋を更新します
Jan の予約で部屋を更新します
Pat は一時間部屋を予約しましたが、Jan の更新操作は最後に実行されたため、Pat の予約を上書きしました。
競合条件がない例
次の例では、bookARoomSafe() 関数を使用して、ゲストを部屋にチェックインします。この関数は、findOneAndUpdate() を使用して検索操作と更新操作を 1 つのアトミック操作として実行します。
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
Pat は次の出力を確認します。
Sorry, we are booked, Pat
データベースを見ると、次のドキュメントが表示されます。
{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}
Jan が部屋を予約し、Pat は部屋が利用できないことを示す正しいメッセージを受け取りました。MongoDB が実行した操作の順序は次のとおりです。
1 月日の空き間を探して予約する
Pat の空き部屋を探して予約してください
空きの部屋を探し、値を出力せずに完了します
findOneAndUpdate() は検索と更新をアトミックに実行するため、Jan の予約が完了した後に Atlas App Services の操作では利用可能なリージョンを読み取ることができません。
重要
書込みロック (write lock)
MongoDB インスタンスは、複合操作の実行中、変更しているドキュメントに対して書込みロック (write lock) を配置します。
このセクションで使用されるメソッドとクラスの詳細については、次の API ドキュメントを参照してください。