Menu Docs

Transações e Sessões

Neste guia, você aprenderá a usar o Mongoid para realizar transações. As transações permitem que você execute uma série de operações que alteram os dados somente se toda a transação estiver confirmada. Se qualquer operação na transação não for bem-sucedida, o driver interromperá a transação e descartará todas as alterações de dados antes que elas se tornem visíveis. Esse recurso é chamado de atomicidade.

No MongoDB, as transações são executadas dentro de sessões lógicas . Uma sessão é um agrupamento de operações de leitura ou escrita relacionadas que você deseja executar sequencialmente. As sessões permitem consistência causal para um grupo de operações, o que significa que todos os processos em seu aplicação concordam com a ordem das operações causalmente relacionadas.

As sessões permitem que você execute operações em uma transação compatível com ACID que atenda a uma expectativa de atomicidade, consistência, isolamento e durabilidade. O MongoDB garante que os dados envolvidos em suas operações de transação permaneçam consistentes, mesmo que as operações encontrem erros inesperados.

No Mongoid, você pode realizar transações usando uma das seguintes APIs:

A seção API de sessão descreve como fazer alterações em seus dados a partir de uma sessão sem executar uma transação.

Você pode usar a API de transação de alto nível para gerenciar internamente o ciclo de vida de sua transação. Essa API confirma sua transação ou a encerra e incorpora lógica de tratamento de erros.

Você pode iniciar uma transação chamando o método transaction em uma instância de um modelo, na classe de modelo ou em um módulo Mongoid.

Quando você chama o método transaction, o Mongoid executa as seguintes tarefas:

  1. Cria uma sessão no cliente.

  2. Inicia uma transação na sessão.

  3. Executa as alterações de dados especificadas.

  4. Confirma a transação no banco de dados se não ocorrerem erros ou termina a transação se houver um erro.

  5. Encerra a sessão.

Se sua transação estiver confirmada, o Mongoid chamará quaisquer after_commit chamada de resposta para todos os objetos modificados dentro da transação. Se houver um erro e a transação for revertida, o Mongoid chamará qualquer chamada de resposta after_rollback para todos os objetos modificados dentro da transação. Para saber mais sobre essas chamadas de resposta e seu comportamento, consulte a seção Chamadas de resposta deste guia.

Este exemplo utiliza os seguintes modelos para representar documentos que descrevem livros e filmes:

class Book
include Mongoid::Document
field :title, type: String
field :author, type: String
field :length, type: Integer
end
class Film
include Mongoid::Document
field :title, type: String
field :year, type: Integer
end

O código a seguir demonstra como realizar uma transação em diferentes objetos para alterar dados em várias coleções:

# Starts a transaction from the model class
Book.transaction do
# Saves new Book and Film instances to MongoDB
Book.create(title: 'Covert Joy', author: 'Clarice Lispector')
Film.create(title: 'Nostalgia', year: 1983)
end
# Starts a transaction from an instance of Book
book = Book.create(title: 'Sula', author: 'Toni Morrison')
book.transaction do
# Saves a new field value to the Book instance
book.length = 192
book.save!
end
# Starts a transaction from the Mongoid instance
Mongoid.transaction do
# Deletes the Book instance in MongoDB
book.destroy
end

Somente as operações no mesmo cliente estão no escopo de uma transação, pois cada transação está vinculada a um cliente específico. Certifique-se de usar objetos do mesmo cliente dentro do bloco de método de transação.

O exemplo a seguir define classes de modelo que usam clientes diferentes e demonstra como as operações são executadas com base no cliente de origem:

# Defines a class by using the :default client
class Post
include Mongoid::Document
end
# Defines a class by using the :encrypted_client
class User
include Mongoid::Document
store_in client: :encrypted_client
end
# Starts a transaction on the :encrypted_client
User.transaction do
# Uses the same client, so the operation is in the transaction
User.create!
# Uses a different client, so it is *not* in the transaction
Post.create!
end

Observação

Quando você chama o método transaction no módulo Mongoid, o Mongoid cria a transação usando o cliente :default.

Qualquer exceção gerada dentro do bloco do método de transação encerra a transação e reverte as alterações de dados. O Mongoid exibe todas as exceções, exceto a exceção Mongoid::Errors::Rollback. Você pode gerar essa exceção em seu aplicação para encerrar explicitamente a transação sem retornar a exceção para você. Por exemplo, você pode implementar essa exceção de transação para encerrar uma transação quando uma determinada condição não for atendida, mas sem gerar uma mensagem de exceção.

Esta API de transação introduz as chamadas de resposta after_commit e after_rollback.

O Mongoid aciona a chamada de resposta after_commit para um objeto que foi criado, salvo ou excluído nos seguintes casos:

  • Depois que a transação for confirmada, o objeto foi modificado dentro da transação.

  • Depois que o objeto for persistente se o objeto tiver sido modificado fora do bloco de transação.

A chamada de resposta after_commit é acionada somente depois que todas as outras chamadas de resposta forem realizadas com sucesso. Portanto, se um objeto for modificado fora de uma transação, é possível que o objeto seja persistente, mas a chamada de resposta after_commit não seja acionada. Isso pode ocorrer, por exemplo, se o Mongoid tiver gerado uma exceção na chamada de resposta after_save porque essa chamada de resposta deve ser concluída com sucesso para acionar after_commit.

A chamada de resposta after_rollback é acionada para um objeto que foi criado, salvo ou excluído dentro de uma transação, se a transação não tiver sido bem-sucedida e as alterações tiverem sido revertidas. O Mongoid nunca aciona after_rollback fora de uma transação.

To learn more about callbacks, see the Customize Callbacks for Data Models guide.

Ao usar a API de baixo nível, você deve criar uma sessão antes de iniciar uma transação. Você pode criar uma sessão chamando o método with_session em uma classe de modelo ou em uma instância de um modelo.

Em seguida, você pode iniciar uma transação chamando o método start_transaction em uma sessão. Ao usar esta API, você deve confirmar ou encerrar a transação manualmente. Você pode utilizar os métodos commit_transaction e abort_transaction na instância de sessão para gerenciar o ciclo de vida da transação.

Este exemplo usa a API de transação de baixo nível para executar as seguintes ações:

  1. Cria uma sessão

  2. inicia uma transação

  3. Executa operações de dados

  4. Confirma a transação ou a encerra se houver erros

# Starts a session from the model class
Book.with_session do |session|
session.start_transaction
# Creates a Book
Book.create(title: 'Siddhartha', author: 'Hermann Hesse')
# Commits the transaction
session.commit_transaction
rescue StandardError
# Ends the transaction if there is an error
session.abort_transaction
end

Observação

Se uma sessão terminar e incluir uma transação aberta, a transação será automaticamente encerrada.

Você pode tentar novamente a confirmação da transação se ela falhar inicialmente. O exemplo a seguir demonstra como tentar novamente a transação quando o Mongoid gerar a exceção UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL:

begin
session.commit_transaction
rescue Mongo::Error => e
if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)
retry
else
raise
end
end

Você pode especificar uma preocupação de leitura, preocupação de gravação ou preferência de leitura ao iniciar uma transação passando opções para o método start_transaction:

session.start_transaction(
read_concern: {level: :majority},
write_concern: {w: 3},
read: {mode: :primary}
)

Para saber mais sobre as opções de transação disponíveis, consulte start_transaction na documentação da API do driver Ruby.

Para realizar operações dentro de uma transação, as operações devem usar o mesmo cliente em que a sessão foi iniciada. Por padrão, todas as operações são executadas usando o cliente padrão.

Para usar explicitamente um cliente diferente, use o método with :

# Specifies that the operation should use the "other" client instead of
# the default client
User.with(client: :other) do
Post.with(client: :other) do
Post.with_session do |session|
session.start_transaction
Post.create!
Post.create!
User.create!
session.commit_transaction
end
end
end

Você pode usar sessões no Mongoid de maneira semelhante a que pode realizar uma transação. Você pode chamar o método with_session em uma classe de modelo ou em uma instância de um modelo e executar algumas operações em um bloco. Todas as operações no bloco serão realizadas no contexto de sessão única.

As seguintes limitações se aplicam ao usar sessões:

  • Não é possível compartilhar uma sessão entre threads. As sessões não são seguras para threads.

  • Não é possível aninhar sessões. Por exemplo, você não pode chamar o método with_session em uma classe de modelo dentro do bloco passado para o método with_session em outra classe de modelo. O seguinte código demonstra uma sessão aninhada que resulta em um erro:

    Book.with_session(causal_consistency: true) do
    # Nesting sessions results in errors
    Film.with_session(causal_consistency: true) do
    ...
    end
    end
  • Todas as classes e instâncias de modelo usadas no bloco de sessão devem usar o mesmo driver cliente. Por exemplo, se você especificar um cliente diferente para um modelo usado no bloco do modelo ou instância que você chamou de with_session, o Mongoid retornará um erro.

Você pode usar o método with_session em uma classe de modelo e passar as opções de sessão para executar um bloco de operações no contexto de uma sessão.

O código a seguir ativa a opção causal_consistency para garantir a ordem das operações ao criar uma sessão no modelo Book e, em seguida, executar operações de dados:

Book.with_session(causal_consistency: true) do
Book.create!
book = Person.first
book.title = "Swann's Way"
book.save
end

Para saber mais sobre as opções de sessão disponíveis, consulte os detalhes do construtor da classe Session na documentação da API do driver Ruby.

Como alternativa, você pode usar o método with_session em uma instância de um modelo e passar as opções de sessão para executar um bloco de operações no contexto de uma sessão.

O código a seguir habilita a opção causal_consistency para garantir a ordem das operações ao criar uma sessão em uma instância de Book e, em seguida, executar operações de dados:

book = Book.new
book.with_session(causal_consistency: true) do
book.title = 'Catch-22'
book.save
book.sellers << Shop.create!
end

Para saber mais sobre transações, consulte Transações no manual do servidor.

Para saber mais sobre como executar operações CRUD, consulte o guia Executar operações de dados.