Visão geral
Neste guia, você pode aprender a realizar operações compostas com o driver Scala.
As operações compostas consistem em uma operação de leitura e uma operação de gravação executadas como uma operação atômica. Uma operação atômica é uma operação que é concluída totalmente ou não é concluída.
As operações atômicas podem ajudar a evitar condições de corrida em seu código. Uma condição de corrida ocorre quando o comportamento do seu código depende da ordem de eventos incontroláveis.
O MongoDB é compatível com as seguintes operações compostas:
Localizar e atualizar um documento
Localizar e substituir um documento
Encontrar e excluir um documento
Para obter mais informações sobre operações atômicas e atomicidade, consulte Atomicidade e transações no manual do MongoDB Server.
Dica
Transações
Se você precisar executar tarefas mais complexas atomicamente, como ler e gravar em mais de um documento, use transações. As transações permitem que você execute uma sequência de comandos de banco de dados como uma operação atômica. Para obter mais informações sobre transações, consulte Transações no manual do MongoDB Server.
Como usar operações compostas
Esta seção mostra como usar o driver Scala para executar operações compostas.
Os exemplos a seguir usam uma coleção que contém dois documentos de amostra:
{"_id": {"$oid": "1"}, "food": "donut", "color": "green"} {"_id": {"$oid": "2"}, "food": "pear", "color": "yellow"}
Os exemplos usam a seguinte referência collection para mapear cada documento para uma instância da classe Document:
val database: MongoDatabase = mongoClient.getDatabase("compound_operations") val collection: MongoCollection[Document] = database.getCollection("example") val hotelCollection: MongoCollection[Document] = database.getCollection("rooms")
Cada operação composta retorna um SingleObservable[Document] que emite o documento correspondente ou é concluída sem emitir um valor se nenhum documento corresponder ao filtro de queries. Se o filtro de queries corresponder a vários documentos, o método atuará na primeira correspondência, onde first se refere à ordem natural no disco, a menos que você especifique uma ordem de classificação no objeto de opções correspondente.
Observação
Retornar documentos após a operação de gravação
Por padrão, uma operação composta retorna o documento correspondente e, em seguida, executa a operação de gravação nele. Você pode direcionar a operação composta para executar a operação de gravação primeiro, passando um objeto de opções para o método correspondente. O exemplo Localizar e substituir mostra essa configuração.
Encontrar e atualizar
Para encontrar e atualizar um documento, use o método findOneAndUpdate() da classe MongoCollection.
Exemplo
O exemplo a seguir usa o método findOneAndUpdate() para localizar um documento onde o campo color tem o valor "green". Em seguida, ele atualiza o campo food nesse documento para "pizza".
O exemplo também utiliza uma instância do FindOneAndUpdateOptions para especificar as seguintes opções:
Especifique um upsert, que insere o documento especificado pelo filtro de query se nenhum documento corresponder à query.
Defina um tempo máximo de execução de 5 segundos para esta operação na instância do MongoDB. Se a operação demorar mais, o método
findOneAndUpdate()emitirá umMongoExecutionTimeoutExceptionpor meio da chamada de respostaonError()do observador.
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
Para obter mais informações sobre os métodos e as classes usados nesta seção, consulte a seguinte documentação da API:
Encontrar e substituir
Para encontrar e substituir um documento, use o método findOneAndReplace() da classe MongoCollection.
Exemplo
O exemplo a seguir usa o método findOneAndReplace() para localizar um documento onde o campo color tem o valor "green". Ele substitui o documento correspondente por
{"music": "classical", "color": "green"}
O exemplo também usa uma instância FindOneAndReplaceOptions para especificar que o documento retornado deve estar no estado após a operação de substituição.
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
Para obter mais informações sobre os métodos e as classes usados nesta seção, consulte a seguinte documentação da API:
Localize e exclua
Para encontrar e excluir um documento, use o método findOneAndDelete() da classe MongoCollection.
Exemplo
O exemplo a seguir utiliza o método findOneAndDelete() para localizar e excluir o documento com o maior valor no campo _id.
O exemplo utiliza uma instância FindOneAndDeleteOptions para especificar uma classificação decrescente no campo _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
Para obter mais informações sobre os métodos e as classes usados nesta seção, consulte a seguinte documentação da API:
Evitando uma condição de corrida
Ao executar várias operações atomicamente, as operações compostas ajudam a evitar condições de corrida.
Para os exemplos nesta seção, imagine que você gerencia um hotel com um quarto. Você usa um pequeno programa Scala para fazer o check-in dos hóspedes no quarto.
O documento a seguir representa o quarto de hotel:
{"_id": {"$oid": "1"}, "guest": null, "room": "Blue Room", "reserved": false}
Exemplo com condição de corrida
O exemplo a seguir usa a função bookARoomUnsafe() para fazer o check-in de um hóspede em um quarto. A função executa as operações de localização e atualização separadamente.
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) ) }
Imagine que dois convidados, Jan e Pat, tentam reservar o mesmo quarto ao mesmo tempo.
Jane vê esta saída:
You got the Blue Room, Jan
Pat vê esta saída:
You got the Blue Room, Pat
Ao examinar o banco de dados, você verá o seguinte documento:
{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}
Seu aplicativo informou a Pat e Jan que eles haviam reservado a sala, mas o banco de dados mostra uma reserva apenas para Jan. Aqui está a sequência de operações que o MongoDB executou:
Encontre e devolva o quarto vazio para Jan
Encontre e devolva o quarto vazio para Pat
Atualize a sala com a reserva de Pat
Atualize o quarto com a reserva de Jan
Embora Pat tenha reservado o quarto por um momento, a operação de atualização de Jan sobrescreveu a reserva de Pat porque foi a última a ser executada.
Exemplo sem condição de corrida
O exemplo a seguir usa a função bookARoomSafe() para fazer o check-in de um hóspede em um quarto. A função usa findOneAndUpdate() para executar as operações de localização e atualização como uma única operação atômica.
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") }
Imagine que dois convidados, Jan e Pat, tentam reservar o mesmo quarto ao mesmo tempo.
Jane vê esta saída:
You got the Blue Room, Jan
Pat vê esta saída:
Sorry, we are booked, Pat
Ao examinar o banco de dados, você verá o seguinte documento:
{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}
Jan reservou a sala e Pat recebeu a mensagem correta de que não há salas disponíveis. Aqui está a sequência de operações que o MongoDB executou:
Encontre um quarto vazio para Jan e reserve-o
Tente encontrar e reservar uma sala vazia para Pat
Não encontre salas vazias e conclua sem emitir um valor
Como findOneAndUpdate() executa a pesquisa e atualiza atomicamente, a operação de Pat não pode ler a sala como disponível após a conclusão da reserva de Jan.
Importante
bloqueio de gravação
Sua instância do MongoDB coloca um bloqueio de gravação no documento que você está modificando durante a operação composta.
Para obter mais informações sobre os métodos e as classes usados nesta seção, consulte a seguinte documentação da API: