Operações compostas
Nesta página
Visão geral
Neste guia, você pode aprender como realizar operações compostas com o driver MongoDB Kotlin.
As operações compostas consistem em uma operação de leitura e gravação executada 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 não podem ser concluídas parcialmente.
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
Se você precisar executar tarefas mais complexas atomicamente, como ler e gravar em mais de um documento, use transações. As transação são uma funcionalidade do MongoDB e de outros reconhecimento de data center que permitem definir uma sequência arbitrária de reconhecimento de data center como uma operação atômica.
Para obter mais informações sobre operações atômicas e atomicidade, consulte a entrada manual do MongoDB para atomicidade e transações.
Para obter mais informações sobre transações, consulte a entrada manual do MongoDB para transações.
Como usar operações compostas
Esta seção mostra como usar cada operação composta com o MongoDB Kotlin Driver.
Os exemplos a seguir usam uma collection que contém esses dois documentos de amostra.
{"_id": 1, "food": "donut", "color": "green"} {"_id": 2, "food": "pear", "color": "yellow"}
Esses dados são modelados com a seguinte classe de dados Kotlin:
data class FoodOrder( val id: Int, val food: String, val color: String )
Observação
Antes ou depois da gravação?
Por padrão, cada operação composta retorna o documento encontrado no estado anterior à operação de gravação. Você pode recuperar o documento encontrado no estado após a operação de gravação usando a classe de opções correspondente à operação composta. Você pode ver um exemplo dessa configuração no exemplo Localizar e Substituir abaixo.
Encontrar e atualizar
Para localizar e atualizar um documento, utilize o método findOneAndUpdate()
da classe MongoCollection
. O método findOneAndUpdate()
retorna o documento encontrado ou null
se nenhum documento corresponder à sua query.
Exemplo
O exemplo a seguir usa o método findOneAndUpdate()
para localizar um documento com o campo color
definido como "green"
e atualizar 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 cinco segundos para esta operação na instância do MongoDB. Se a operação demorar mais, o método
findOneAndUpdate()
lançará umMongoExecutionTimeoutException
.
val filter = Filters.eq(FoodOrder::color.name, "green") val update = Updates.set(FoodOrder::food.name, "pizza") val options = FindOneAndUpdateOptions() .upsert(true) .maxTime(5, TimeUnit.SECONDS) /* The result variable contains your document in the state before your update operation is performed or null if the document was inserted due to upsert being true */ val result = collection.findOneAndUpdate(filter, update, options) println(result)
FoodOrder(id=1, food=donut, color=green)
Para obter mais informações sobre a classe Projections
, consulte nosso guia no construtor de projeções.
Para obter mais informações sobre a operação upsert, consulte nosso guia sobre upserts.
Para obter mais informações sobre os métodos e as classes mencionadas nesta seção, consulte a seguinte documentação da API:
Encontrar e substituir
Para localizar e substituir um documento, use o método findOneAndReplace()
da classe MongoCollection
. O método findOneAndReplace()
retorna o documento encontrado ou null
se nenhum documento corresponder à sua query.
Exemplo
O exemplo a seguir usa o método findOneAndReplace()
para localizar um documento com o campo color
definido como "green"
e substituí-lo pelo seguinte documento:
{"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 nossa operação de substituição.
data class Music( val id: Int, val music: String, val color: String ) val filter = Filters.eq(FoodOrder::color.name, "green") val replace = Music(1, "classical", "green") val options = FindOneAndReplaceOptions() .returnDocument(ReturnDocument.AFTER) val result = collection.withDocumentClass<Music>().findOneAndReplace(filter, replace, options) println(result)
Music(id=1, music=classical, color=green)
Para obter mais informações sobre os métodos e as classes mencionadas nesta seção, consulte a seguinte documentação da API:
Localize e exclua
Para localizar e excluir um documento, use o método findOneAndDelete()
da classe MongoCollection
. O método findOneAndDelete()
retorna o documento encontrado ou null
se nenhum documento corresponder à sua query.
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 sort = Sorts.descending("_id") val filter = Filters.empty() val options = FindOneAndDeleteOptions().sort(sort) val result = collection.findOneAndDelete(filter, options) println(result)
FoodOrder(id=2, food=pear, color=yellow)
Para obter mais informações sobre a classe Sorts
, consulte nosso guia sobre o construtor de classificações.
Para obter mais informações sobre os métodos e as classes mencionadas nesta seção, consulte a seguinte documentação da API:
Evitando uma condição de corrida
Nesta seção, exploraremos dois exemplos. O primeiro exemplo contém uma condição de corrida, o segundo exemplo utiliza uma operação composta para evitar a condição de corrida presente no primeiro exemplo.
Para ambos os exemplos, vamos imaginar que administramos um hotel com um quarto e que temos um pequeno programa Kotlin para nos ajudar a fazer o checkout desse quarto para um convidado.
O seguinte documento no MongoDB representa a divisão:
{"_id": 1, "guest": null, "room": "Blue Room", "reserved": false}
Esses dados são modelados com a seguinte classe de dados Kotlin:
data class HotelRoom( val id: Int, val guest: String? = null, val room: String, val reserved: Boolean = false )
Exemplo com condição de corrida
Digamos que nosso aplicativo use esse método bookARoomUnsafe
para fazer o checkout do nosso quarto para um convidado:
suspend fun bookARoomUnsafe(guestName: String) { val filter = Filters.eq("reserved", false) val myRoom = hotelCollection.find(filter).firstOrNull() if (myRoom == null) { println("Sorry, we are booked, $guestName") return } val myRoomName = myRoom.room println("You got the $myRoomName, $guestName") val update = Updates.combine(Updates.set("reserved", true), Updates.set("guest", guestName)) val roomFilter = Filters.eq("_id", myRoom.id) hotelCollection.updateOne(roomFilter, update) }
Imagine dois convidados separados, Jane e Ana, tentando agendar o quarto com esse método ao mesmo tempo.
Jane vê esta saída:
You got the Blue Room, Jan
EPat vê esta saída:
You got the Blue Room, Pat
Quando analisamos nosso reconhecimento de data center, vemos o seguinte:
{"_id": 1, "guest": "Jan", "room": "Blue Room", "reserved": false}
Pat ficará chateada. QuandoPapa aparecer em nosso hotel,Jan estará ocupando o quarto dela. O que deu errado?
Aqui está a sequência de eventos que aconteceram da perspectiva de nossa instância do MongoDB:
Encontre e devolva um quarto vazio para janeiro.
Encontre e devolva um quarto vazio paraPat.
Atualize o quarto como reservado paraPat.
Atualizar o quarto como reservado em Janeiro.
Observe que, por um breve momento, Ana reservou a divisão, mas como a operação de atualização de Janeiro foi a última a executar, nosso documento tem "Jan"
como convidado.
Exemplo sem condição de corrida
Vamos usar uma operação composta para evitar a condição de corrida e sempre dar aos nossos usuários a mensagem correta.
suspend fun bookARoomSafe(guestName: String) { val update = Updates.combine( Updates.set(HotelRoom::reserved.name, true), Updates.set(HotelRoom::guest.name, guestName) ) val filter = Filters.eq("reserved", false) val myRoom = hotelCollection.findOneAndUpdate(filter, update) if (myRoom == null) { println("Sorry, we are booked, $guestName") return } val myRoomName = myRoom.room println("You got the $myRoomName, $guestName") }
Imagine dois convidados separados, Jane e Ana, tentando agendar o quarto com esse método ao mesmo tempo.
Jane vê esta saída:
You got the Blue Room, Jan
EPat vê esta saída:
Sorry, we are booked, Pat
Quando analisamos nosso reconhecimento de data center, vemos o seguinte:
{"_id": 1, "guest": "Jan", "room": "Blue Room", "reserved": false}
Pat recebeu a mensagem correta. Embora possa estar triste por não ter conseguido a reserva, pelo menos sabe que não deve viajar para o nosso hotel.
Aqui está a sequência de eventos que aconteceram da perspectiva de nossa instância do MongoDB:
Encontre um quarto vazio para Jane e reserve-o.
Tente encontrar um quarto vazio paraPat e reservá-lo.
Quando não houver mais salas, devolva
null
.
Importante
bloqueio de gravação
Sua instância do MongoDB coloca um bloqueio de escrita no documento que você está modificando durante a operação composta.
Para obter informações sobre a classe Updates
, consulte nosso guia sobre o construtor de atualizações.
Para obter mais informações sobre a classe Filters
, consulte nosso guia sobre o construtor de filtros.
Para obter mais informações sobre o findOneAndUpdate()
método , consulte a documentação da API para a classe MongoCollection.