técnica tecnica tabla protocolo por multiple granularidad fases datos control concurrencia bloqueos bloqueo jpa transactions locking

tecnica - JPA y modos de bloqueo optimistas.



tecnica de control de concurrencia (3)

Leí un artículo en el blog de Oracle here sobre JPA y los modos de bloqueo.

No entiendo completamente la diferencia entre los tipos de modo de bloqueo OPTIMISTIC y OPTIMISTIC_FORCE_INCREMENT .

Modo OPTIMISTIC :

Cuando un usuario bloquea una entidad con este modo, se realiza una comprobación en la entidad del campo de versión ( @version ) al comienzo de la transacción y también se realiza una comprobación en el campo de la versión al final de la transacción. Si las versiones son diferentes, la transacción se retrotrae.

Modo OPTIMISTIC_FORCE_INCREMENT :

Cuando un usuario elige este modo, tiene que vaciar () el estado de EntityManager en la base de datos para incrementar el campo de la versión manualmente. Por lo tanto, todas las demás transacciones optimistas se invalidarán (revertirán). También se realiza una comprobación de la versión al final de la transacción para confirmar o deshacer la transacción.

Parece claro, pero ¿cuándo debo usar los modos OPTIMISTIC en lugar de OPTIMISTIC_FORCE_INCREMENT ? El único criterio que veo es aplicar el modo OPTIMISTIC_FORCE_INCREMENT cuando quiero que la transacción tenga prioridad sobre las demás porque al elegir este modo se revertirán todas las demás transacciones en ejecución (si entiendo bien el mecanismo).

¿Hay OPTIMISTIC otra razón para elegir este modo en lugar del modo OPTIMISTIC ?

Gracias


Como se explica en esta publicación , LockModeType.OPTIMICTIC de verificación de la acción, por lo que es mejor que lo asocie con PESSIMISTIC_READ o PESSIMISTIC_WRITE .

Por otro lado, LockModeType.OPTIMICTIC_FORCE_INCREMENT no sufre ningún problema de inconsistencia de datos, y normalmente lo usaría para controlar la versión de una entidad principal cada vez que se modifica una entidad secundaria.

Echa un vistazo a este artículo para obtener más detalles sobre cómo puedes usar LockModeType.OPTIMICTIC_FORCE_INCREMENT o LockModeType.PESSIMISTIC_FORCE_INCREMENT para que la versión de la entidad principal tenga en cuenta que la entidad secundaria también cambia.


No te asustes por esta larga respuesta. Este tema no es simple.

De forma predeterminada, JPA impone el nivel de aislamiento de lectura confirmada si no especifica ningún bloqueo (el mismo comportamiento que con LockModeType.NONE ).

La lectura comprometida requiere la no existencia del fenómeno de la lectura sucia . Simplemente T1 solo puede ver los cambios realizados por T2 después de T2 confirmados.

El uso del bloqueo optimista en JPA eleva el nivel de aislamiento a las lecturas de Repetable .

Si T1 lee algunos datos al principio y al final de la transacción, Repetable lee asegura que T1 ve los mismos datos, incluso si T2 cambió los datos y los confirmó en medio de T1.

Y aquí viene la parte difícil. JPA logra lecturas Repetable de la manera más simple posible: al prevenir el fenómeno de lectura No Repetable . JPA no es lo suficientemente sofisticado como para mantener instantáneas de tus lecturas. Simplemente evita que ocurra la segunda lectura aumentando una excepción (si los datos han cambiado desde la primera lectura).

Puede elegir entre dos opciones de bloqueo optimistas:

  • LockModeType.OPTIMISTIC ( LockModeType.READ en JPA 1.0)

  • LockModeType.OPTIMISTIC_FORCE_INCREMENT ( LockModeType.WRITE en JPA 1.0)

¿Cuál es la diferencia entre los dos?

Permítanme ilustrar con ejemplos en esta entidad de Person .

@Entity public class Person { @Id int id; @Version int version; String name; String label; @OneToMany(mappedBy = "person", fetch = FetchType.EAGER) List<Car> cars; // getters & setters }

Ahora supongamos que tenemos una Persona llamada John almacenada en la base de datos. Leemos esta Persona en T1, pero cambiamos su nombre a Mike en la segunda transacción T2.

Sin ningún bloqueo

Person person1 = em1.find(Person.class, id, LockModeType.NONE); //T1 reads Person("John") Person person2 = em2.find(Person.class, id); //T2 reads Person("John") person2.setName("Mike"); //Changing name to "Mike" within T2 em2.getTransaction().commit(); // T2 commits System.out.println(em1.find(Person.class, id).getName()); // prints "John" - entity is already in Persistence cache System.out.println( em1.createQuery("SELECT count(p) From Person p where p.name=''John''") .getSingleResult()); // prints 0 - ups! don''t know about any John (Non-repetable read)

Bloqueo de lectura optimista

Person person1 = em1.find(Person.class, id, LockModeType.OPTIMISTIC); //T1 reads Person("John") Person person2 = em2.find(Person.class, id); //T2 reads Person("John") person2.setName("Mike"); //Changing name to "Mike" within T2 em2.getTransaction().commit(); // T2 commits System.out.println( em1.createQuery("SELECT count(p) From Person p where p.name=''John''") .getSingleResult()); // OptimisticLockException - The object [Person@2ac6f054] cannot be updated because it has changed or been deleted since it was last read. LockModeType.OPTIMISTIC_FORCE_INCREMENT se usa cuando el cambio se realiza en otra entidad (tal vez una relación no propia) y queremos preservar la integridad. Déjame ilustrar con John adquiriendo un auto nuevo.

Bloqueo de lectura optimista

Person john1 = em1.find(Person.class, id); //T1 reads Person("John") Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC); //T2 reads Person("John") //John gets a mercedes Car mercedes = new Car(); mercedes.setPerson(john2); em2.persist(mercedes); john2.getCars().add(mercedes); em2.getTransaction().commit(); // T2 commits //T1 doesn''t know about John''s new car. john1 in stale state. We''ll end up with wrong info about John. if (john1.getCars().size() > 0) { john1.setLabel("John has a car"); } else { john1.setLabel("John doesn''t have a car"); } em1.flush();

Bloqueo de escritura optimista

Person john1 = em1.find(Person.class, id); //T1 reads Person("John") Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC_FORCE_INCREMENT); //T2 reads Person("John") //John gets a mercedes Car mercedes = new Car(); mercedes.setPerson(john2); em2.persist(mercedes); john2.getCars().add(mercedes); em2.getTransaction().commit(); // T2 commits //T1 doesn''t know about John''s new car. john1 in stale state. That''s ok though because proper locking won''t let us save wrong information about John. if (john1.getCars().size() > 0) { john1.setLabel("John has a car"); } else { john1.setLabel("John doesn''t have a car"); } em1.flush(); // OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

Aunque hay una observación siguiente en la especificación JPA, Hibernate y EclipseLink se comportan muy bien y no lo usan.

Para los objetos versionados, está permitido que una implementación use LockMode-Type.OPTIMISTIC_FORCE_INCREMENT donde se solicitó LockModeType.OPTIMISTIC, pero no al revés.


Normalmente, nunca usaría la API lock () para un bloqueo optimista. JPA verificará automáticamente las columnas de la versión en cualquier actualización o eliminación.

El único propósito de la API lock () para un bloqueo optimista es cuando su actualización depende de otro objeto que no se modifica / actualiza. Esto permite que su transacción aún falle si el otro objeto cambia.

Cuando hacer esto depende de la aplicación y el caso de uso. OPTIMISTIC se asegurará de que el otro objeto no se haya actualizado en el momento de su confirmación. OPTIMISTIC_FORCE_INCREMENT asegurará que el otro objeto no se haya actualizado, e incrementará su versión en la confirmación.

El bloqueo optimista siempre se verifica en el compromiso, y no hay garantía de éxito hasta el compromiso. Puede usar flush () para forzar los bloqueos de la base de datos antes de tiempo, o desencadenar un error anterior.