Visão geral
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.
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 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 :
inte aIntegerlonge aLongInstant
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.
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 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.
Lidar com 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 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.
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 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.
Bloqueio 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 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}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 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.