Visão geral
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.
Adicionar um campo de versão
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:
public class Product { private ObjectId id; private String name; private int quantity; 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 :
inte aIntegerlonge aLongInstant
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.
Atualizar uma entidade versionada
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.
Gerenciar um conflito de versã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.
Incrementar uma versão sem alterar os dados
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.
Bloquear sem um campo de versão
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:
public class Product { 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:
public class Product { private ObjectId id; private String name; private int quantity; private Long version; // Constructors, getters, and setters }
Limitações
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
@Versionpara o métodoStatelessSession.upsert(). A extensão ORM do Hibername lança umUnsupportedFeatureExceptionpara esta operação.
Informações adicionais
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.