synchronized WebService

Que tal, el día de hoy se le presentó el siguiente reto a un compañero:

Agregar a un WS existente un método generador de Folios numéricos únicos.
Los folios nacen de un SP en oracle (del cual no se tiene el control)

Este es el método del WS

 

El primer intento

El primer intento que hizo fue especificar el método de ServIdentificador como synchronized

 

Horror! >_< Al hacer una prueba concurrente los folios se repetían.
El problema era que instanciaba la clase ServIdentificador cada que se invocaba el WS y el LOCK era local.

El segundo intento

Le recomendé que hiciera este pequeño cambio:

 

Esta implementación pasó la prueba de concurrencia (aunque se sacrificó tiempo de respuesta) .

Mi pregunta aquí es:
Cuál sería la mejor forma de hacer esto?
O sea.. que harían ustedes =)

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

no hay mucho

No hay mucho para dónde moverle. Si están haciendo varias instancias, pues o hacen un lock a nivel clase como ya mostraste, o hacen un lock a nivel base de datos.

Así como hicieron varias instancias dentro de la misma aplicación... es posible que usen esa clase en varias aplicaciones? Porque si la usan en dos o más aplicaciones, ya sea en el mismo equipo o en equipos distintos, tendrán nuevamente el problema de los folios repetidos. Para evitar eso, la única opción es irse a la fuente: la base de datos. Bloquear a nivel base de datos.

Cómo bloquear a nivel base de datos? Si no pueden ni ver siquiera el código del SP, pues tendrán que hacer una prueba manual: dos sesiones de línea de comando a la base de datos, iniciar transacción en ambas, ejecutar el SP en una y luego en la otra. La segunda debería de quedarse bloqueada, hasta que hagan commit en la primera y entonces la segunda les debe dar un folio distinto al que obtuvieron en la primera sesión. Dan commit a la segunda y listo.

Si lo anterior funciona, entonces no es necesario hacer ningún tipo de bloqueo a nivel Java, sino que mejor ejecutan el código de SQL dentro de una transacción (ya les dejo de tarea si lo hacen por código o por configuración con anotaciones o como sea).

Si lo anterior no funciona, pueden arreglarlo complicando un poco el asunto: Ejecutan la llamada al SP dentro de una transacción, pero antes de invocar el SP, ya con la transacción abierta, hace un SELECT FOR UPDATE a alguna tabla que existe únicamente para este propósito y que tiene un solo registro. Puede ser una tabla de una sola columna, tipo entero, con un solo registro, un 1. Abren transacción (con aislamiento READ_COMMITTED) y ejecutan   y luego ejecutan el stored procedure.

La concurrencia se resuelve porque cualquier objeto que use el componente para los folios, en cualquier aplicación, en cualquier equipo de su red, ejecutará el mismo código: primero inicia una transacción y hace un SELECT FOR UPDATE a la tabla, lo cual bloquea el registro que ésta contiene, por lo que en caso de varias llamadas simultáneas, solamente la primera que alcance a hacer el SELECT podrá ejecutar la siguiente instrucción, que es obtener el folio. Luego de eso hace commit, y en ese momento se libera la tabla (que no fue modificada por cierto, no es necesario). Entonces una de todas las demás transacciones que están bloqueadas obtendrá el bloqueo de la misma, y luego obtendrá un folio (nuevo, porque todas las demás transacciones están esperando) y así con cada transacción que esté esperando.

La manera de probarlo es igual, con dos sesiones manuales, o si ya es prueba de su componente, pues métanle un Thread.sleep() o algo justo antes del commit, para que puedan hacer una primera invocación y mientras ese hilo se queda dormido, hacen una segunda (y si quieran, tercera, cuarta, quinta...) invocación, y deben ver que se quedan bloqueadas en el SELECT (si ponen un maravilloso log o println después del SELECT, solamente deben ver uno, hasta que se haga commit de la primera invocación, y entonces verán el println de la siguiente, etc).