style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

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

public   Integer servIdentificador(@WebParam(name = "idServicio")Integer idServicio){
    ServIdentificador serIden= new ServIdentificador();    
    Integer resp = serIden.getIdentificador(idServicio);
    return resp;
}

El primer intento

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

public class ServIdentificador {
     public synchronized Integer getIdentificador(Integer idServicio) throws SQLException {
         // code code more code
     }
}

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:

public class ServIdentificador throws SQLException {
       
        private static final Object singleLock = new Object();
       
        public Integer getIdentificador(Integer idServicio)  {
               
                Integer folio;
                synchronized (singleLock) {
                        // code code more code... asignacion de folio y manejo de Excepciones
                }
                return folio;
        }
}

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 SELECT * FROM tabla_bloqueo WHERE columna=1 FOR UPDATE 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).

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">