hibernate - objetos - Bloqueo optimista que no arroja excepción cuando se configura manualmente el campo de versión
objetos hibernate (3)
Tengo una aplicación web Spring Boot 1.3.M1 que utiliza Spring Data JPA. Para un bloqueo optimista, estoy haciendo lo siguiente:
- Anote la columna de la versión en la entidad:
@Version private long version;
. Confirmé, al mirar la tabla de la base de datos, que este campo se está incrementando correctamente. - Cuando un usuario solicita una entidad para su edición, también envía el campo de
version
. - Cuando el usuario presiona enviar después de la edición, recibe el campo de
version
como un campo oculto o algo así. Del lado del servidor, obteniendo una copia nueva de la entidad y luego actualizando los campos deseados, junto con el campo de
version
. Me gusta esto:User user = userRepository.findOne(id); user.setName(updatedUser.getName()); user.setVersion(updatedUser.getVersion()); userRepository.save(user);
Esperaba que arrojara una excepción cuando las versiones no coincidían. Pero no es así Google, encontré algunas publicaciones que decían que no podemos establecer la propiedad @Vesion
de una entidad adjunta, como hago en la tercera declaración anterior.
Entonces, supongo que tendré que verificar manualmente si la versión no coincide y lanzar la excepción yo mismo. ¿Sería esa la forma correcta, o me falta algo?
Parte de la respuesta de @AdrianShum es correcta.
La versión que compara el comportamiento sigue básicamente estos pasos:
- Recupere la entidad versionada con su número de versión, permite llamar a V1.
- Supongamos que modifica la propiedad de alguna entidad, luego Hibernate incrementa el número de versión a V2 "en la memoria". No toca la base de datos.
- Usted confirma los cambios o el entorno los compromete automáticamente, luego Hibernate intentará actualizar la entidad, incluido su número de versión con el valor V2. La consulta de actualización generada por Hibernate modificará el registro de la entidad solo si coincide con el ID y el número de versión anterior (V1).
- Después de que el registro de la entidad se modifique con éxito, la entidad toma V2 como su valor de versión real.
Ahora supongamos que entre los pasos 1 y 3 la entidad fue modificada por otra transacción, por lo que su número de versión en el paso 3 no es V1. Entonces, como el número de versión es diferente, la consulta de actualización no modificará ningún registro, hibernate se dará cuenta de eso y lanzará la excepción.
Simplemente puede probar este comportamiento y verificar que la excepción sea alterar el número de versión directamente en su base de datos entre los pasos 1 y 3.
Editar. No sé qué proveedor de persistencia JPA está utilizando con Spring Data JPA, pero para más detalles sobre el bloqueo optimista con JPA + Hibernate, le sugiero que lea el capítulo 10, sección Controlar el acceso simultáneo , del libro Java Persistence con Hibernate (Hibernate en acción )
Desafortunadamente, (al menos para Hibernate) cambiar manualmente el campo @Version
no lo convertirá en otra "versión". es decir, la comprobación de concurrencia optimista se realiza frente al valor de versión recuperado cuando se lee la entidad, no el campo de versión de la entidad cuando se actualiza.
p.ej
Esto funcionará
Foo foo = fooRepo.findOne(id); // assume version is 2 here
foo.setSomeField(....);
// Assume at this point of time someone else change the record in DB,
// and incrementing version in DB to 3
fooRepo.flush(); // forcing an update, then Optimistic Concurrency exception will be thrown
Sin embargo, esto no funcionará
Foo foo = fooRepo.findOne(id); // assume version is 2 here
foo.setSomeField(....);
foo.setVersion(1);
fooRepo.flush(); // forcing an update, no optimistic concurrency exception
// Coz Hibernate is "smart" enough to use the original 2 for comparison
Hay alguna forma de solucionar esto. La forma más directa es probablemente mediante la implementación de la comprobación de concurrencia optimista por ti mismo. Solía tener un util para hacer la población de datos "DTO to Model" y allí puse esa lógica de verificación de versiones. Otra forma es poner la lógica en setVersion()
que, en lugar de establecer realmente la versión, haga la verificación de la versión:
class User {
private int version = 0;
//.....
public void setVersion(int version) {
if (this.version != version) {
throw new YourOwnOptimisticConcurrencyException();
}
}
//.....
}
Además de la respuesta de @Adrian Shum, quiero mostrar cómo resolví este problema. Si desea cambiar manualmente una versión de Entity y realizar una actualización para causar OptimisticConcurrencyException
, puede simplemente copiar Entity con todo su campo, lo que hace que una entidad abandone su contexto (igual que EntityManager.detach()
). De esta manera, se comporta de forma adecuada.
Entity entityCopy = new Entity();
entityCopy.setId(id);
... //copy fields
entityCopy.setVersion(0L); //set invalid version
repository.saveAndFlush(entityCopy); //boom! OptimisticConcurrencyException
EDITAR: la versión ensamblada funciona, solo si la memoria caché de hibernación no contiene entidad con el mismo ID. Esto no funcionará:
Entity entityCopy = new Entity();
entityCopy.setId(repository.findOne(id).getId()); //instance loaded and cached
... //copy fields
entityCopy.setVersion(0L); //will be ignored due to cache
repository.saveAndFlush(entityCopy); //no exception thrown