Para agentes de IA: um índice de documentação está disponível em https://www.mongodb.com/pt-br/docs/llms.txt — as versões de markdown de todas as páginas estão disponíveis anexando .md a qualquer caminho de URL.
Menu Docs

Operações compostas

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.

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.

Para encontrar e atualizar um documento, use o método findOneAndUpdate() da classe MongoCollection.

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á um MongoExecutionTimeoutException por meio da chamada de resposta onError() 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:

Para encontrar e substituir um documento, use o método findOneAndReplace() da classe MongoCollection.

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:

Para encontrar e excluir um documento, use o método findOneAndDelete() da classe MongoCollection.

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:

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}

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:

  1. Encontre e devolva o quarto vazio para Jan

  2. Encontre e devolva o quarto vazio para Pat

  3. Atualize a sala com a reserva de Pat

  4. 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.

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:

  1. Encontre um quarto vazio para Jan e reserve-o

  2. Tente encontrar e reservar uma sala vazia para Pat

  3. 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: