java - tabla - Por qué mi bloqueo pesimista en JPA con Oracle no funciona
tabla de bloqueo base de datos (2)
Establezca el modo de bloqueo en PESSIMISTIC_READ, porque necesita que el segundo servidor tenga conocimiento de los cambios del primer servidor antes de cambiar los datos.
Estoy tratando de implementar algún tipo de semáforo para trabajos cron que se ejecute en diferentes nodos JBoss. Estoy intentando utilizar la base de datos (Oracle 11g) como un mecanismo de bloqueo usando una tabla para sincronizar las tareas cron en los diferentes nodos. La mesa es muy simple:
CREATE TABLE SYNCHRONIZED_CRON_JOB_TASK
(
ID NUMBER(10) NOT NULL,
CRONJOBTYPE VARCHAR2(255 Byte),
CREATIONDATE TIMESTAMP(6) NOT NULL,
RUNNING NUMBER(1)
);
ALTER TABLE SYNCHRONIZED_CRON_JOB_TASK
ADD CONSTRAINT PK_SYNCHRONIZED_CRON_JOB_TASK
PRIMARY KEY (ID);
Por lo tanto, cuando se inicia un trabajo, busca en la tabla una entrada de su cronjobtype y comprueba si ya se está ejecutando. Si no, actualiza el indicador de ejecución de la configuración de entrada a verdadero. Esta primera selección se realiza con JPA CriteriaApi usando Hibernate y Pessimistic Lock.
query.setLockMode(javax.persistence.LockModeType.PESSIMISTIC_WRITE);
Todas esas operaciones se hacen dentro de una transacción.
Cuando se ejecuta un proceso, las consultas que hace son las siguientes:
[Server:server-two] 10:38:00,049 INFO [stdout] (scheduler-2) 2015-04-30 10:38:00,048 WARN (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,049 INFO [stdout] (scheduler-2) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-two] 10:38:00,053 INFO [stdout] (scheduler-2) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-two] 10:38:00,056 INFO [stdout] (scheduler-2) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?
No hay problema con esta advertencia, puede ver una primera selección y luego seleccionar para la actualización, por lo que Oracle debe bloquear otras operaciones de selección en esta fila. Pero ese es el punto, las consultas no se están bloqueando para que dos trabajos puedan ingresar y hacer la selección y actualización sin problemas. El bloqueo no funciona, podemos verlo si ejecutamos dos trabajos cron al mismo tiempo:
[Server:server-one] 10:38:00,008 INFO [stdout] (scheduler-3) 2015-04-30 10:38:00,008 WARN (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,008 INFO [stdout] (scheduler-2) 2015-04-30 10:38:00,008 WARN (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,009 INFO [stdout] (scheduler-2) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-one] 10:38:00,009 INFO [stdout] (scheduler-3) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-two] 10:38:00,013 INFO [stdout] (scheduler-2) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-one] 10:38:00,014 INFO [stdout] (scheduler-3) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-two] 10:38:00,016 INFO [stdout] (scheduler-2) 2015-04-30 10:38:00,015 DEBUG (SynchronizedCronJobService.java:65) - Task read SynchronizedCronJobTask [id=185, type=AlertMailTaskExecutor, creationDate=2015-04-25 07:11:33.0, running=false]
[Server:server-two] 10:38:00,018 INFO [stdout] (scheduler-2) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?
[Server:server-one] 10:38:00,022 INFO [stdout] (scheduler-3) 2015-04-30 10:38:00,022 DEBUG (SynchronizedCronJobService.java:65) - Task read SynchronizedCronJobTask [id=185, type=AlertMailTaskExecutor, creationDate=2015-04-25 07:11:33.0, running=false]
[Server:server-one] 10:38:00,024 INFO [stdout] (scheduler-3) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?
Intenté hacer que esto se seleccione para la actualización en una herramienta SQL (SQLWorkbenchJ) con dos conexiones y el bloqueo funciona bien dentro de esta herramienta. Pero si selecciono esta opción para actualizar en la herramienta SQL e iniciar las tareas cron, no están bloqueadas y se ejecutan sin problemas.
Creo que el problema proviene de JPA, Hibernate o el controlador de Oracle, pero no estoy seguro. Alguna idea de donde esta el problema? ¿Debo usar una estrategia anotehr? Gracias por adelantado.
Finalmente logré hacerlo funcionar pero con algunas modificaciones. La idea es usar LockModeType.PESSIMISTIC_FORCE_INCREMENT en lugar de PESSIMISTIC_WRITE. Al usar este modo de bloqueo, Cron Jobs se comporta de la siguiente manera:
- Cuando el primer trabajo realiza la selección de actualización, todo sale como se espera, pero la versión del objeto cambia.
- Si otro trabajo intenta hacer la misma selección mientras el primero todavía está en su transacción, JPA lanza una OptimisticLockException, por lo que si detecta esa excepción, puede estar seguro de que fue lanzada para un bloqueo de lectura.
Esta solución tiene varias contrapartes:
- SynchronizedCronJobTask debe tener un campo de versión y estar bajo control de versión con @Version
- Debe manejar OptimisticLockException, y debe ser capturado fuera del método de servicio transaccional para realizar una reversión cuando ocurre el bloqueo.
- En mi humilde opinión, la IMHO es una solución no elegante, mucho peor que simplemente un bloqueo en el que Cron Jobs espera a que los trabajos anteriores terminen.