Para agentes de IA: hay un índice de documentación disponible en https://www.mongodb.com/es/docs/llms.txt — versiones en markdown de todas las páginas están disponibles agregando .md a cualquier ruta URL.
Docs Menu

Evite actualizaciones conflictivas con bloqueo optimista.

En esta guía, aprenderá a usar la extensión MongoDB para Hibernate ORM para implementar el bloqueo optimista. El bloqueo optimista permite que varias sesiones simultáneas lean la misma entidad y detecta un conflicto cuando más de una sesión intenta escribir en ella.

Cuando se activa el bloqueo optimista, la extensión Hibernate ORM genera una instrucción MQL (Lenguaje de Consulta de MongoDB) para cada operación de actualización o eliminación. Esta instrucción filtra según el valor de versión que leyó la sesión, además de la clave primaria. Si otra sesión modificó el documento previamente, el filtro no coincide con ningún documento y Hibernate ORM genera una excepción OptimisticLockException.

La extensión Hibernate ORM no admite el bloqueo pesimista. El bloqueo pesimista bloquea una entidad cuando una sesión la lee, lo que impide que otras sesiones modifiquen la entidad hasta que la sesión libere el bloqueo.

Para activar el bloqueo optimista para una entidad, agregue un campo anotado con @Version a la clase de la entidad.

Define un método getter para el campo si tu aplicación lee el valor de la versión, pero no definas un método setter. El ORM de Hibernate asigna el valor automáticamente, y establecer el campo en el código de tu aplicación podría provocar que la extensión del ORM de Hibernate genere un filtro que no coincida con el documento almacenado.

Los ejemplos de esta guía utilizan la siguiente entidad Product, que se corresponde con la colección products. No inicialice el campo version, ya que el ORM de Hibernate asigna su valor inicial cuando persiste la entidad por primera 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;
}
}

La extensión Hibernate ORM admite los siguientes tipos para un campo @Version:

  • int y Integer

  • long y Long

  • Instant

Un campo de versión numérica comienza en 0 y se incrementa en 1 con cada escritura. Un campo de versión Instant almacena la hora de la escritura.

Al persistir una nueva entidad, el ORM de Hibernate establece el campo de versión en 0. Al modificar la entidad y confirmar la transacción, la extensión del ORM de Hibernate genera una instrucción de actualización que filtra tanto la clave primaria como el valor de versión leído por la sesión. Esta instrucción también establece el nuevo valor de versión.

El siguiente ejemplo inserta una nueva entidad Product y luego actualiza su campo quantity para incrementar la versión:

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();

La actualización del ejemplo anterior genera una instrucción similar a la siguiente MQL:

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

Las operaciones de eliminación utilizan el mismo filtro. Dado que el valor de la versión forma parte del filtro, la escritura solo se realiza correctamente si el documento aún conserva la versión que leyó la sesión.

Si otra sesión modifica un documento después de que su sesión lo haya leído, el valor de versión en su filtro ya no coincide con el valor almacenado, y el ORM de Hibernate lanza una excepción OptimisticLockException. Para solucionarlo, capture la excepción y recargue la entidad e intente escribir de nuevo, o bien devuelva un error que notifique al usuario de su aplicación sobre el conflicto.

El siguiente ejemplo utiliza dos sesiones para leer el mismo documento. La segunda sesión realiza la confirmación primero, por lo que la confirmación en la primera sesión genera una excepción 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();

Nota

Dependiendo de cómo su aplicación cargue la entidad, el ORM de Hibernate podría lanzar una excepción StaleObjectStateException. Esta excepción es el equivalente nativo del ORM de Hibernate a OptimisticLockException.

Para incrementar la versión de una entidad que su sesión no modificó, pase la entidad y el modo de bloqueo LockMode.OPTIMISTIC_FORCE_INCREMENT al método lock() de su Session. Utilice este modo de bloqueo cuando un cambio en los datos relacionados deba invalidar las copias de la entidad de otras sesiones.

El siguiente ejemplo incrementa la versión de un 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();

Si otra sesión incrementó la versión primero, la confirmación arroja un OptimisticLockException.

También puedes detectar conflictos sin agregar un campo de versión. Para ello, anota tu entidad con @OptimisticLocking y especifica uno de los siguientes tipos:

  • OptimisticLockType.DIRTY: La extensión Hibernate ORM filtra según los valores anteriores únicamente de los campos que la sesión modificó.

  • OptimisticLockType.ALL: La extensión Hibernate ORM filtra según los valores anteriores de todos los campos de la entidad.

Ambos tipos requieren la anotación @DynamicUpdate.

El siguiente ejemplo utiliza el 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 un campo del bloqueo optimista en una entidad versionada, anote el campo con @OptimisticLock(excluded = true). Los cambios en un campo excluido no incrementan la versión:

@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
}

El bloqueo optimista tiene las siguientes limitaciones:

  • La extensión Hibernate ORM no admite el bloqueo pesimista. Para obtener más información sobre la compatibilidad con el bloqueo, consulte la sección Transacciones y concurrencia de la página Compatibilidad de funciones.

  • No se puede pasar una entidad que tenga un campo @Version al método StatelessSession.upsert(). La extensión Hibernate ORM lanza una excepción UnsupportedFeatureException para esta operación.

Para obtener más información sobre el bloqueo optimista, consulte la sección "Bloqueo optimista" en la documentación de Hibernate ORM.

Para obtener más información sobre cómo ejecutar operaciones de escritura en una transacción, consulte la guía de Transacciones y Sesiones.