Menu Docs
Página inicial do Docs
/ / /
Kotlin Coroutine
/ /

Operações compostas

Nesta página

  • Visão geral
  • Como usar operações compostas
  • Encontrar e atualizar
  • Encontrar e substituir
  • Localize e exclua
  • Evitando uma condição de corrida
  • Exemplo com condição de corrida
  • Exemplo sem condição de corrida

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.

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(
@BsonId 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.

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.

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á um MongoExecutionTimeoutException.

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:

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.

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(
@BsonId 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:

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.

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:

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(
@BsonId val id: Int,
val guest: String? = null,
val room: String,
val reserved: Boolean = false
)

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:

  1. Encontre e devolva um quarto vazio para janeiro.

  2. Encontre e devolva um quarto vazio paraPat.

  3. Atualize o quarto como reservado paraPat.

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

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:

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

  2. Tente encontrar um quarto vazio paraPat e reservá-lo.

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

Voltar

Query