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.

Evite atualizações conflitantes com o bloqueio otimista

Neste guia, você pode aprender como usar a MongoDB Extension para Hibernate ORM para realizar bloqueio otimista. O bloqueio otimista permite que sessões simultâneas leiam a mesma entidade e, em seguida, detecta um conflito quando mais de uma sessão tenta gravar nessa entidade.

Quando você ativa o bloqueio otimista, a extensão Hibernate ORM gera uma declaração da linguagem de query MongoDB (MQL) para cada operação de atualizar ou excluir. A declaração filtra pelo valor da versão que a sessão leu, além da chave primária. Se outra sessão modificou o documento primeiro, o filtro não corresponde a nenhum documento e o Hibernate ORM gera uma OptimisticLockException.

A extensão Hibernate ORM não oferece suporte a bloqueio pessimista. O bloqueio pessimista bloqueia um documento quando uma sessão o lê, o que impede que outras sessões modifiquem o documento até que a sessão libere o bloqueio.

Para ativar o bloqueio otimista para uma entidade, adicione um campo anotado com @Version à classe da entidade.

Defina um getter para o campo se seu aplicativo ler o valor da versão, mas não defina um setter. O Hibernate ORM atribui o valor por conta própria, e definir o campo no código do seu aplicativo pode fazer com que a extensão do Hibernate ORM gere um filtro que não corresponda ao documento armazenado.

Os exemplos neste guia usam a seguinte entidade Product, que mapeia para a coleção products. Não inicialize o campo version, porque o Hibernate ORM atribui seu valor inicial quando você persiste a entidade pela primeira vez:

@Entity@Table(name = "products")public class Product { @Id @ObjectIdGenerator private ObjectId id; private String name; private int quantity; @Version private Long version; public Product() { } public Product(String name, int quantity) { this.name = name; this.quantity = quantity; } public ObjectId getId() { return id; } public Long getVersion() { return version; } public void setQuantity(int quantity) { this.quantity = quantity; }}

A extensão Hibernar ORM suporta os seguintes tipos para um campo@Version :

  • int e a Integer

  • long e a Long

  • Instant

Um campo de versão numérica começa em 0 e aumenta em 1 a cada gravar. Um campo de versão Instant armazenar o horário do gravar.

Quando você persiste uma nova entidade, o Hibername ORM define o campo de versão como 0. Quando você, em seguida, modifica a entidade e confirma a transação, a extensão ORM do Hibername gera uma instrução de atualização que filtra a chave primária e o valor da versão que a sessão lê. A declaração também define o valor da nova versão.

O exemplo a seguir insere uma nova entidade Product e, em seguida, atualiza seu campo quantity para incrementar a versão:

var sf = HibernateUtil.getSessionFactory();try (Session session = sf.openSession()) { // Insert a new product, which sets the version to 0 session.beginTransaction(); var product = new Product("notebook", 100); session.persist(product); session.getTransaction().commit(); System.out.println("Version after insert: " + product.getVersion()); // Update the same product, which increments the version to 1 session.beginTransaction(); product.setQuantity(75); session.getTransaction().commit(); System.out.println("Version after update: " + product.getVersion());}sf.close();

A atualização no exemplo anterior gera uma declaração semelhante ao seguinte MQL:

{ "update": "products", "updates": [ { "q": { "$and": [ { "_id": { "$eq": "..." } }, { "version": { "$eq": 0 } } ] }, "u": { "$set": { "quantity": 75, "version": 1 } } } ]}

As operações de excluir usam o mesmo filtro. Como o valor da versão faz parte do filtro, gravar só é bem-sucedida se o documento ainda mantiver a versão que a sessão leu.

Se outra sessão modificar um documento depois que a sua sessão lê-lo, o valor da versão no seu filtro não corresponde mais ao valor armazenado, e o ORM do Hibername lançará um OptimisticLockException. Para recuperar, capture a exceção e recarregue a entidade e tente novamente a gravação, ou retorne um erro que notifique o usuário do aplicativo sobre o conflito.

O exemplo a seguir usa duas sessões para ler o mesmo documento. A segunda sessão é confirmada primeiro, portanto, a confirmação na primeira sessão gera um OptimisticLockException:

var sf = HibernateUtil.getSessionFactory();ObjectId productId; // Insert a new product for two sessions to modifytry (Session session = sf.openSession()) { session.beginTransaction(); var product = new Product("notebook", 100); session.persist(product); session.getTransaction().commit(); productId = product.getId();} try (Session sessionA = sf.openSession(); Session sessionB = sf.openSession()) { // Both sessions load the product at version 0 var productA = sessionA.find(Product.class, productId); var productB = sessionB.find(Product.class, productId); // Session B commits first and increments the version to 1 sessionB.beginTransaction(); productB.setQuantity(75); sessionB.getTransaction().commit(); // Session A commits second, but still holds version 0 try { sessionA.beginTransaction(); productA.setQuantity(50); sessionA.getTransaction().commit(); } catch (OptimisticLockException e) { System.out.println("Update failed: Another session modified this document"); // Reload the entity and retry, or report the conflict to the user }}sf.close();

Observação

Dependendo de como seu aplicação carrega a entidade, o ORM do Hibernar pode lançar um StaleObjectStateException. Essa exceção é o equivalente nativo do Hibername ORM de OptimisticLockException.

Para incrementar a versão de uma entidade que sua sessão não modificou, passe a entidade e o modo de bloqueio LockMode.OPTIMISTIC_FORCE_INCREMENT para o método lock() em seu Session. Use este modo de bloqueio quando uma alteração nos dados relacionados precisar invalidar as cópias de outras sessões da entidade.

O exemplo a seguir incrementa a versão de um documento Product:

var sf = HibernateUtil.getSessionFactory();try (Session session = sf.openSession()) { session.beginTransaction(); var product = session.createQuery("from Product where name = :n", Product.class) .setParameter("n", "notebook") .setMaxResults(1) .getSingleResult(); session.lock(product, LockMode.OPTIMISTIC_FORCE_INCREMENT); session.getTransaction().commit();}sf.close();

Se outra sessão incrementou a versão primeiro, o commit lança um OptimisticLockException.

Você também pode detectar conflitos sem adicionar um campo de versão. Para fazer isso, anote sua entidade com @OptimisticLocking e especifique um dos seguintes tipos:

  • OptimisticLockType.DIRTY: A extensão Hibernate ORM aplica filtro nos valores anteriores apenas dos campos que a sessão modificou.

  • OptimisticLockType.ALL: A extensão ORM do Hibername filtra os valores anteriores de todos os campos da entidade.

Ambos os tipos exigem a anotação @DynamicUpdate.

O exemplo a seguir usa o tipo DIRTY:

@Entity@Table(name = "products")@OptimisticLocking(type = OptimisticLockType.DIRTY)@DynamicUpdatepublic class Product { @Id @ObjectIdGenerator private ObjectId id; private String name; private int quantity; // Constructors, getters, and setters}

Para excluir um campo do bloqueio otimista em uma entidade com controle de versão, anote o campo com @OptimisticLock(excluded = true). As alterações em um campo excluído não incrementam a versão:

@Entity@Table(name = "products")public class Product { @Id @ObjectIdGenerator private ObjectId id; @OptimisticLock(excluded = true) private String name; private int quantity; @Version private Long version; // Constructors, getters, and setters}

O bloqueio otimista tem as seguintes limitações:

  • A extensão Hibername ORM não suporta bloqueio pessimista. Para saber mais sobre o suporte de bloqueio, consulte a seção Transações e simultaneidade da página Compatibilidade de recursos.

  • Você não pode passar uma entidade que tenha um campo @Version para o método StatelessSession.upsert(). A extensão ORM do Hibername lança um UnsupportedFeatureException para esta operação.

Para aprender mais sobre o bloqueio otimista, consulte Bloqueio Otimista na documentação do ORM do Hibernate.

Para aprender mais sobre como executar operações de gravação em uma transação, consulte o guia Transações e Sessões.