Introduccion
¡Entonces comencemos! ¿Qué significa la
anotación de versión en JPA?
En resumen, ella es responsable del bloqueo en la JPA. Esta anotación resuelve uno de los problemas que pueden surgir como resultado de transacciones paralelas.
¿Qué problemas pueden surgir?
- Las actualizaciones perdidas pueden ocurrir en situaciones en las que dos transacciones que se ejecutan en paralelo intentan actualizar los mismos datos.
- Las lecturas sucias ocurren cuando una transacción ve cambios que aún no han sido guardados por otra transacción. En tales casos, puede ocurrir un problema debido a la reversión de la segunda transacción, pero los datos ya se han leído primero.
- Las lecturas no repetibles se producen cuando la primera transacción ha recibido datos y la segunda transacción ha realizado cambios en ellos y los ha confirmado con éxito hasta el final de la primera transacción. En otras palabras, cuando dentro de la misma transacción, la misma solicitud para obtener, por ejemplo, la tabla completa, devuelve resultados diferentes.
- La lectura fantasma es un problema similar a las lecturas repetidas, con la excepción de que se devuelve un número diferente de líneas.
Brevemente sobre sus decisiones
- LEA SIN COMPROMISO: resuelto utilizando la anotación de versión en JPA (este es exactamente el artículo)
- LEER COMPROMETIDO: permite leer solo los cambios confirmados
- LECTURA REPETIBLE: un poco más complicado aquí. Nuestra transacción "no ve" cambios en los datos que leyó anteriormente, y otras transacciones no pueden cambiar los datos que cayeron en nuestra transacción.
- SERIALIZABLE - ejecución secuencial de transacciones
Cada párrafo posterior cubre todos los anteriores, en otras palabras, puede reemplazar las soluciones indicadas anteriormente. Por lo tanto, SERIALIZABLE tiene el nivel de aislamiento más alto y READ UNCOMMITED tiene el nivel más bajo.
Versión
La versión resuelve el problema con las
actualizaciones perdidas . Cómo exactamente, ahora ya veremos.
Antes de pasar al código, vale la pena mencionar que hay dos tipos de bloqueos:
optimista y
pesimista . La diferencia es que las primeras se guían por situaciones en las que muchas transacciones intentan cambiar un campo al mismo tiempo, surgen extremadamente raramente, mientras que otras se guían por la situación opuesta. De acuerdo con esto, hay una diferencia en su lógica de ejecución.
En bloqueos
optimistas , cuando se compromete con la base de datos, el valor del campo marcado como versión se compara en el momento en que se reciben los datos y en el momento. Si ha cambiado, es decir, alguna otra transacción está por delante de la nuestra y logró cambiar los datos, en este caso nuestra transacción arroja un error y debe reiniciarlo.
Cuando se utilizan bloqueos
optimistas , se garantiza un mayor nivel de competitividad al acceder a la base de datos, pero en este caso debe repetir las transacciones que no tuvieron tiempo de realizar cambios antes que otras.
En los
pesimistas , el bloqueo se impone inmediatamente antes de la supuesta modificación de datos en todas las líneas que dicha modificación supuestamente afecta.
Y cuando se usan bloqueos
pesimistas , la ausencia de contradicciones durante la ejecución de la transacción está garantizada, debido a que el resto está en modo de espera (pero esto lleva tiempo), como resultado, bajando el nivel de competencia.
LockModeType o cómo establecer un bloqueo
El bloqueo se puede establecer llamando al método de apariencia de EntityManager.
entityManager.lock(myObject, LockModeType.OPTIMISTIC);
LockModeType define una estrategia de bloqueo.
LockModeType puede ser de 6 tipos (2 de los cuales son
optimistas y 3
pesimistas ):
- NINGUNO - sin bloqueo
- OPTIMISTA
- OPTIMISTIC_FORCE_INCREMENT
- PESIMISTA_ LEER
- ESCRITURA PESIMISTA
- PESSIMISTIC_FORCE_INCREMENT
Crea nuestra entidad import lombok.Getter; import lombok.Setter; import javax.persistence.*; @EntityListeners(OperationListenerForMyEntity.class) @Entity public class MyEntity{ @Version private long version; @Id @GeneratedValue @Getter @Setter private Integer id; @Getter @Setter private String value; @Override public String toString() { return "MyEntity{" + "id=" + id + ", version=" + version + ", value='" + value + '\'' + '}'; } }
Creemos una clase donde se implementarán todos los métodos de devolución de llamada. import javax.persistence.*; public class OperationListenerForMyEntity { @PostLoad public void postLoad(MyEntity obj) { System.out.println("Loaded operation: " + obj); } @PrePersist public void prePersist(MyEntity obj) { System.out.println("Pre-Persistiting operation: " + obj); } @PostPersist public void postPersist(MyEntity obj) { System.out.println("Post-Persist operation: " + obj); } @PreRemove public void preRemove(MyEntity obj) { System.out.println("Pre-Removing operation: " + obj); } @PostRemove public void postRemove(MyEntity obj) { System.out.println("Post-Remove operation: " + obj); } @PreUpdate public void preUpdate(MyEntity obj) { System.out.println("Pre-Updating operation: " + obj); } @PostUpdate public void postUpdate(MyEntity obj) { System.out.println("Post-Update operation: " + obj); } }
Main.java import javax.persistence.*; import java.util.concurrent.*; // , . public class Main { // , .. EntityManagerFactory , . private static EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("ru.easyjava.data.jpa.hibernate"); public static void main(String[] args) { // 10 ( , ). ExecutorService es = Executors.newFixedThreadPool(10); try { // persistFill() - . persistFill(); for(int i=0; i<10; i++){ int finalI = i; es.execute(() -> { // updateEntity(finalI) , java . java - , id, , id , ( , persistFill(), id 500). updateEntity(finalI); }); } es.shutdown(); try { es.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } finally { entityManagerFactory.close(); } } // . private static void updateEntity(int index) { // EntityManager , , . EntityManager em = entityManagerFactory.createEntityManager(); MyEntity myEntity = null; try { em.getTransaction().begin(); // 1. myEntity = em.find(MyEntity.class, 1); // sout, "" . System.out.println("load = "+index); // ( LockModeType.*). em.lock(myEntity, LockModeType.OPTIMISTIC); // Value, , . myEntity.setValue("WoW_" + index); em.getTransaction().commit(); em.close(); System.out.println("--Greeter updated : " + myEntity +" __--__ "+ index); }catch(RollbackException ex){ System.out.println(", =" + myEntity); } } public static void persistFill() { MyEntity myEntity = new MyEntity(); myEntity.setValue("JPA"); EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.persist(myEntity); em.getTransaction().commit(); em.close(); } }
Primera ejecución con el método updateEntity comentado Pre-Persistiting operation: MyEntity{id=null, version=0, value='JPA'} Post-Persist operation: MyEntity{id=531, version=0, value='JPA'} . id find .
LockModeType.OPTIMISTIC
Este es un bloque
optimista , bueno, esto es muy lógico. Como escribí anteriormente, se compara el valor del campo de versión; si es diferente, se produce un error. Mira esto.
Resultados: Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 3 Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 2 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_2'} Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_3'} Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 9 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_9'} Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 1 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_1'} Post-Update operation: MyEntity{id=531, version=1, value='WoW_1'} --Greeter updated : MyEntity{id=531, version=1, value='WoW_1'} __--__ 1 , =MyEntity{id=531, version=0, value='WoW_2'} , =MyEntity{id=531, version=0, value='WoW_3'} Loaded operation: MyEntity{id=531, version=1, value='WoW_1'} load = 4 Pre-Updating operation: MyEntity{id=531, version=1, value='WoW_4'} Post-Update operation: MyEntity{id=531, version=2, value='WoW_4'} --Greeter updated : MyEntity{id=531, version=2, value='WoW_4'} __--__ 4 , =MyEntity{id=531, version=0, value='WoW_9'} Loaded operation: MyEntity{id=531, version=2, value='WoW_4'} load = 0 Pre-Updating operation: MyEntity{id=531, version=2, value='WoW_0'} Post-Update operation: MyEntity{id=531, version=3, value='WoW_0'} --Greeter updated : MyEntity{id=531, version=3, value='WoW_0'} __--__ 0 Loaded operation: MyEntity{id=531, version=3, value='WoW_0'} load = 6 Pre-Updating operation: MyEntity{id=531, version=3, value='WoW_6'} Post-Update operation: MyEntity{id=531, version=4, value='WoW_6'} Loaded operation: MyEntity{id=531, version=4, value='WoW_6'} load = 5 Pre-Updating operation: MyEntity{id=531, version=4, value='WoW_5'} Post-Update operation: MyEntity{id=531, version=5, value='WoW_5'} --Greeter updated : MyEntity{id=531, version=4, value='WoW_6'} __--__ 6 --Greeter updated : MyEntity{id=531, version=5, value='WoW_5'} __--__ 5 Loaded operation: MyEntity{id=531, version=5, value='WoW_5'} load = 7 Pre-Updating operation: MyEntity{id=531, version=5, value='WoW_7'} Post-Update operation: MyEntity{id=531, version=6, value='WoW_7'} Loaded operation: MyEntity{id=531, version=5, value='WoW_5'} load = 8 Pre-Updating operation: MyEntity{id=531, version=5, value='WoW_8'} --Greeter updated : MyEntity{id=531, version=6, value='WoW_7'} __--__ 7 , =MyEntity{id=531, version=5, value='WoW_8'}
Observaciones: Como puede ver en los resultados, las secuencias 3, 2, 9 y 1 fueron las primeras en cargarse, y se llamaron a los métodos de devolución de llamada previos a la actualización. El primer subproceso donde se llamó al método posterior a la actualización fue 1, como puede ver en los resultados, el campo marcado con la anotación Versión ya ha cambiado (aumentado en 1). En consecuencia, todos los hilos restantes 2, 3, 9 arrojaron una excepción. Y así sucesivamente. El resultado es value = WoW_7, version = 6. De hecho, la última actualización posterior fue en el hilo 7 con version = 6.
LockModeType.OPTIMISTIC_FORCE_INCREMENT
Funciona de acuerdo con el mismo algoritmo que LockModeType.OPTIMISTIC, excepto que después de confirmar el valor del campo Versión se incrementa a la fuerza en 1. Como resultado, después de cada confirmación, el campo aumentará en 2 (el aumento que se puede ver en Post-Actualización + aumento forzado) . Pregunta Por qué Si después de una confirmación queremos "evocar" los mismos datos, y no necesitamos transacciones de terceros que puedan romper entre la primera confirmación y el cierre de nuestra transacción.
Importante! Si intenta cambiar los datos a los mismos, en este caso no se invocarán los métodos Pre-Actualización y Post-Actualización. El colapso de todas las transacciones puede ocurrir. Por ejemplo, se leyeron varias transacciones en paralelo con nosotros, pero dado que las llamadas a los métodos pre y post (actualización) toman tiempo, la transacción que intenta cambiar los datos (a la misma) se ejecutará de inmediato. Esto dará como resultado un error de las transacciones restantes.
LockModeType.PESSIMISTIC_READ, LockModeType.PESSIMISTIC_WRITE y LockModeType.PESSIMISTIC_FORCE_INCREMENT
Dado que el trabajo de los tipos de bloqueos restantes se ve similar, por lo tanto, escribiré sobre todo de una vez y consideraré el resultado solo por PESSIMISTIC_READ.
LockModeType.PESSIMISTIC_READ: bloqueo de lectura
pesimista .
LockModeType.PESSIMISTIC_WRITE: bloqueo de escritura
pesimista (y lectura).
LockModeType.PESSIMISTIC_FORCE_INCREMENT: bloqueo de escritura
pesimista (y lectura) con aumento forzado del campo Versión.
Como resultado de tales bloqueos, puede ocurrir una larga espera para un bloqueo, que a su vez puede conducir a un error.
Resultado en LockModeType.PESSIMISTIC_READ (no presentado en su totalidad): load = 0 Pre-Updating operation: MyEntity{id=549, version=5, value='WoW_0'} Post-Update operation: MyEntity{id=549, version=6, value='WoW_0'} Loaded operation: MyEntity{id=549, version=6, value='WoW_0'} load = 8 Pre-Updating operation: MyEntity{id=549, version=6, value='WoW_8'} Loaded operation: MyEntity{id=549, version=6, value='WoW_0'} load = 4 Pre-Updating operation: MyEntity{id=549, version=6, value='WoW_4'} ... ERROR: : : 22760 ExclusiveLock " (0,66) 287733 271341"; 20876. 20876 ShareLock " 8812"; 22760.
Como resultado, los hilos 4 y 8 se bloquearon entre sí, lo que condujo a un conflicto sin solución. Antes de esto, nadie interfirió con el hilo 0. Una situación similar con todos los hilos hasta 0.