2009-12-20 16 views
8

Actualmente tenemos un bean con estado que se inyecta en un servlet. El problema es que a veces obtenemos un Caused by: javax.ejb.ConcurrentAccessException: SessionBean is executing another request. [session-key: 7d90c02200a81f-752fe1cd-1] al ejecutar un método en el bean con estado.El uso correcto de los beans con estado con servlets

public class NewServlet extends HttpServlet { 
    @EJB 
    private ReportLocal reportBean; 

    protected void processRequest(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException { 
     response.setContentType("text/html;charset=UTF-8"); 
     PrintWriter out = response.getWriter(); 
     try { 
      String[] parameters = fetchParameters(request); 
      out.write(reportBean.constructReport(parameters)); 
     } finally { 
      out.close(); 
     } 
    } 
} 

En el código anterior, constructReport comprobará si tiene que abrir una nueva conexión a la base de datos especificada en el informe después de lo cual un informe en HTML se construye a partir de una consulta que se construye a partir de los parámetros especificados.

La razón por la que elegimos usar un bean stateful sobre un bean sin estado fue porque necesitamos abrir una conexión de base de datos a una base de datos desconocida y realizar consultas sobre ella. Con un bean sin estado, parece terriblemente ineficiente abrir y cerrar repetidamente las conexiones de la base de datos con cada instancia inyectada del bean.

Respuesta

9

Esto no es lo beans de sesión con estado (SFSB) están destinados a ser utilizados para. Están diseñados para mantener el estado de la conversación, y deben estar vinculados a la sesión http del usuario para mantener ese estado, como una alternativa pesada para almacenar el estado en la sesión directamente.

Si desea mantener las cosas como las conexiones de base de datos, entonces hay mejores maneras de ir sobre él.

mejor opción es utilizar un pool de conexiones. Usted debe siempre utilizar un pool de conexiones, y si se está ejecutando dentro de un servidor de aplicaciones (que, si estás usando EJB, entonces es), entonces se puede utilizar fácilmente la configuración de fuente de datos de su servidor de aplicaciones para crear un pool de conexiones, y use eso dentro de su stateless bean de sesión (SLSB).

+0

AFAIK, Al crear un origen de datos en un servidor de aplicaciones, debe saber de antemano que existe la base de datos. La conexión anterior se realiza en una base de datos a la que un usuario especificó debemos conectarnos. – Burmudar

+3

Esto me parece terriblemente inseguro. – duffymo

1

Hasta que proporcione algún código y stacktrace, le sugiero que considere usar un connection pool. Si por "base de datos desconocido" se refiere a una base de datos cuyos parámetros son suministrados por el usuario final, y por lo tanto no hay piscina conexión preconfigurado es posible, aún puede utilizar el concepto de agrupación de conexiones, en lugar de abrir una nueva conexión cada vez.

También, theck this thread.

0

Los beans de sesión no se pueden usar al mismo tiempo, como skaffman dijo que estaban destinados a manejar el estado correspondiente a la sesión del cliente y normalmente se almacenan en el objeto de sesión por cliente.

La refacturación para usar un grupo de bases de datos para gestionar las solicitudes concurrentes a sus recursos es el camino a seguir.

Mientras tanto, si todo lo que necesita es este uso trivial, puede sincronizar la llamada a constructReport como en:

synchronised (reportBean) { 
     out.write(reportBean.constructReport(parameters)); 
} 

Tenga en cuenta que esto no es una solución si constructReport toma una cantidad significativa de tiempo en relación con tu numero de clientes

14

Más detalles sobre ConcurrentAccessException: según la especificación EJB, la aplicación sincroniza el acceso a SLSB. servidor. Sin embargo, este no es el caso con SFSB. La carga de asegurarse de que no se acceda al SFSB simultáneamente está en los hombros del desarrollador de la aplicación.

¿Por qué? Bueno, la sincronización de SLSB solo es necesaria en el nivel de instancia. Es decir, cada instancia particular de SLSB está sincronizada, pero puede tener varias instancias en un grupo o en un nodo diferente en un clúster, y las solicitudes simultáneas en instancias diferentes no son un problema.Desafortunadamente, esto no es tan fácil con SFSB debido a la pasivación/activación de instancias y la replicación en todo el clúster. Esta es la razón por la cual la especificación no hace cumplir esto. Eche un vistazo a this dicussion si está interesado en el tema.

Esto significa que el uso de SFSB del servlet es complicado. Un usuario con múltiples ventanas de la misma sesión o recarga de página antes de que finalice la renderización puede generar acceso concurrente. Cada acceso al EJB que se realiza en un servlet necesita teóricamente estar sincronizado en el mismo bean. Lo que hice fue crear un InvocationHandler para sincronizar todas las invocaciones en la instancia del EJB en particular:

public class SynchronizationHandler implements InvocationHandler { 

private Object target; // the EJB 

public SynchronizationHandler(Object bean) 
{ 
     target = bean; 
} 

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
    { 
    synchronized(target) 
    { 
     // invoke method to the target EJB 
    } 
    } 

}

Entonces, justo después de obtener la referencia remota a la EJB, que se coloca con el SynchronizationHandler . De esta forma, está seguro de que esta instancia particular no se accederá simultáneamente desde su aplicación (siempre que se ejecute en una sola JVM). También puede escribir una clase contenedora regular que sincronice todos los métodos del bean.

Mi conclusión es sin embargo: use SLSB siempre que sea posible.

EDITAR:

Esta respuesta refleja las especificaciones EJB 3.0 (sección 4.3.13):

Los clientes no se les permite hacer llamadas simultáneas a una sesión con estado objeto. Si un método comercial invocado por el cliente está en progreso en una instancia cuando otra llamada invocada por el cliente, del mismo o diferente cliente , llega a la misma instancia de una clase bean de sesión con estado, si el segundo cliente es un cliente de interfaz de negocio de la haba, la invocación concurrente puede resultar en el segundo cliente que recibe el javax.ejb.ConcurrentAccessException

Tales restricciones se han eliminado en EJB 3.1 (sección 4.3.13):

Por defecto, los clientes pueden hacer llamadas simultáneas a un objeto de sesión stateful y el contenedor es necesario para serializar dichas solicitudes simultáneas .

[...]

The Bean desarrollador puede especificar opcionalmente que el cliente concurrentes están prohibidas peticiones a un bean de sesión con estado. Esto se hace usando el elemento del descriptor de despliegue an12Ade anotación de acceso o tiempo de espera @AccessTimeout con un valor de 0. En este caso, si un método de negocio invocado por cliente está en progreso en una instancia cuando otra llamada invocada por el cliente el mismo o diferente cliente, llega a la misma instancia de un bean de sesión con estado , si el segundo cliente es un cliente de la interfaz comercial del bean o vista sin interfaz, la invocación simultánea debe dar como resultado que el segundo cliente reciba un javax.ejb.ConcurrentAccessException

+0

Bueno, no hay necesidad de sincronización una vez que obtiene una referencia a SFSB a través de la búsqueda JNDI, ¿verdad? En este caso, la especificación Java EE garantiza que se devuelve una nueva instancia de SFSB para cada búsqueda. – fnt

+0

@fnt se devuelve una nueva instancia por búsqueda, pero qué hace con usted. El contenedor no serializará invocaciones a la instancia, y puede terminar con una ConcurrentAccessException si no se cuida. – ewernli

+0

Lo que quise decir es suficiente para un uso seguro de buscar SFSB cada vez si no almacena la referencia (ya sea el campo de instancia de servlet o cualquier otro recurso compartido). SynchronizationHandler que describió es superfluo en ese caso. – fnt

0

nunca se debe sincronizar servlet o acceso EJB ya que esto causa peticiones cola y si tiene N al mismo tiempo a los usuarios la última será esperar durante mucho tiempo y con frecuencia conseguir una respuesta de tiempo de espera !!! El método Syncronize no está destinado por este motivo.