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

Evite atualizações conflitantes com o bloqueio otimista

Neste guia, você pode aprender como usar a Extensão MongoDB para Hibernar ORM para executar bloqueios otimistas. 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 ORM do Hibername gera uma instrução MongoDB Query Language (MQL) para cada operação de atualização ou exclusão. A declaração filtra o valor da versão que a sessão lê, além da chave primária. Se outra sessão modificou o documento primeiro, o filtro não corresponde a nenhum documento e o ORM do Hibername lança um OptimisticLockException.

A extensão Hibername ORM não suporta bloqueio pessimista. O bloqueio pessimista bloqueia uma entidade quando uma sessão a lê, o que impede que outras sessões modifiquem a entidade 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 aplicação ler o valor da versão, mas não defina um setter. O ORM do Hibernado atribui o próprio valor, e definir o campo no código do aplicação pode fazer com que a extensão ORM do Hibernado gere um filtro que não corresponde ao documento armazenado.

Os exemplos neste guia usam a seguinte entidade Product, que mapeia para a coleção products. Não inicialize o campoversion, porque o ORM do Hibername 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 em cada gravação. Um campo de versão Instant armazena a hora da escrita.

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 exclusão usam o mesmo filtro. Como o valor da versão faz parte do filtro, uma gravação é bem-sucedida somente se o documento ainda contiver a versão lida pela sessão.

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 utiliza duas sessões para ler o mesmo documento. A segunda sessão é confirmada primeiro, então a confirmação na primeira sessão lança um OptimisticLockException:

var sf = HibernateUtil.getSessionFactory();
ObjectId productId;
// Insert a new product for two sessions to modify
try (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 documentoProduct :

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 ORM do Hibername filtra os 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 utiliza o tipo DIRTY:

@Entity
@Table(name = "products")
@OptimisticLocking(type = OptimisticLockType.DIRTY)
@DynamicUpdate
public 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 saber mais sobre o bloqueio otimista, consulte Bloqueio otimista na documentação do ORM do Hibernado.

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