Correct usage of Stateful Beans with Servlets
Asked Answered
G

5

9

We currently have a Stateful bean that is injected into a Servlet. The problem is that sometimes we get a Caused by: javax.ejb.ConcurrentAccessException: SessionBean is executing another request. [session-key: 7d90c02200a81f-752fe1cd-1] when executing a method on the stateful bean.

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();
        }
    } 
}

In the above code, constructReport will check if it needs to open a new connection to the database specified in the Report after which a Report in HTML is constructed from a query which is built from the parameters specified.

The reason why we chose to use a stateful bean over a stateless bean was because we need to open a database connection to an unknown database and perform queries on it. With a stateless bean it seems terribly inefficient to repeatedly open and close database connections with each injected instance of the bean.

Glomerule answered 20/12, 2009 at 7:53 Comment(0)
D
9

This is not what stateful session beans (SFSB) are intended to be used for. They are designed to hold conversation state, and are to be bound to the user's http session to hold that state, like a heavyweight alternative to storing state in the session directly.

If you want to hold things like database connections, then there are better ways to go about it.

Best option is to use a connection pool. You should always use a connection pool, and if you're running inside an application server (which, if you're using EJBs, then you are), then you can easily use your appserver's datasource configuration to create a connection pool, and use that inside your stateless session bean (SLSB).

Detrition answered 20/12, 2009 at 9:32 Comment(2)
AFAIK, When creating a datasource on an appserver you have to know before hand that the database exists. The above connection is made to a database that a user specified we must connect to.Glomerule
This seems terribly insecure to me.Aguila
J
14

A few more details regarding the ConcurrentAccessException: as per the EJB spec, access to SLSB is synchronized by the app. server. However, this is not the case with SFSB. The burden of making sure that the SFSB is not accessed concurrently is on the application developer's shoulders.

Why? Well, synchronization of SLSB is only necessary at the instance-level. That is, each particular instance of the SLSB is synchronized, but you may have multiple instances in a pool or on different node in a cluster, and concurrent requests on different instances is not a problem. This is unfortunately not so easy with SFSB because of passivation/activation of instances and replication across the cluster. This is why the spec doesn't enforce this. Have a look at this dicussion if you are interested in the topic.

This means that using SFSB from servlet is complicated. A user with multiple windows from the same session, or reloading page before the rendering finished can lead to concurrent access. Each access to the EJB that is done in a servlet needs theoretically to be synchronized on the bean itself. What I did was to to create an InvocationHandler to synchronize all invocations on the particular EJB instance:

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
    }
  }

}

Then, right after you obtain the remote reference to the EJB, you wrap it with the SynchronizationHandler. This way you are sure that this particular instance will not be accessed concurrently from your app (as long as it runs in only one JVM). You can also write a regular wrapper class which synchronizes all the methods of the bean.

My conclusion is nevertheless: use SLSB whenever possible.

EDIT:

This answer reflects the EJB 3.0 specs (section 4.3.13):

Clients are not allowed to make concurrent calls to a stateful session object. If a client-invoked business method is in progress on an instance when another client-invoked call, from the same or different client, arrives at the same instance of a stateful session bean class, if the second client is a client of the bean’s business interface, the concurrent invocation may result in the second client receiving the javax.ejb.ConcurrentAccessException

Such restrictions have been removed in EJB 3.1 (section 4.3.13):

By default, clients are allowed to make concurrent calls to a stateful session object and the container is required to serialize such concurrent requests.

[...]

The Bean Developer may optionally specify that concurrent client requests to a stateful session bean are prohibited. This is done using the @AccessTimeout annotation or access-timeout deployment descriptor element with a value of 0. In this case, if a client-invoked business method is in progress on an instance when another client-invoked call, from the same or different client, arrives at the same instance of a stateful session bean, if the second client is a client of the bean’s business interface or no-interface view, the concurrent invocation must result in the second client receiving a javax.ejb.ConcurrentAccessException

Jolo answered 20/12, 2009 at 10:29 Comment(6)
Well there is no need for synchronization once you obtain a reference to SFSB via JNDI lookup, right? In this case Java EE spec guarantees a new instance of SFSB is returned for each lookup.Bolme
@fnt a new instance is returned per lookup, but what you do with it up to you. The container won't serialize invocations to the instance, and you can end up with a ConcurrentAccessException if you don't take care.Jolo
What I meant it is enough for safe use to lookup SFSB each time if you don't store the reference to it (be it servlet instance field or any other shared resource). SynchronizationHandler that you described is superfluous in that case.Bolme
@fnt right, but if you look it up each time and don't store the reference, what is the SFSB good for? Use a SLSB instead, or even a POJO.Jolo
You are wrong. All invocations to a stateless and a stateful is serialized. Please read more in "4.3.13 Serializing Session Bean Methods" of the EJB specification where it says: "The container serializes calls to each stateful and stateless session bean instance."Teston
@MartinAndersson Good point! Apparently, the EJB 3.0 and 3.1 specs differ when it comes to concurrent accesses to SFSB. I've added a note to the answer. Thanks!Jolo
D
9

This is not what stateful session beans (SFSB) are intended to be used for. They are designed to hold conversation state, and are to be bound to the user's http session to hold that state, like a heavyweight alternative to storing state in the session directly.

If you want to hold things like database connections, then there are better ways to go about it.

Best option is to use a connection pool. You should always use a connection pool, and if you're running inside an application server (which, if you're using EJBs, then you are), then you can easily use your appserver's datasource configuration to create a connection pool, and use that inside your stateless session bean (SLSB).

Detrition answered 20/12, 2009 at 9:32 Comment(2)
AFAIK, When creating a datasource on an appserver you have to know before hand that the database exists. The above connection is made to a database that a user specified we must connect to.Glomerule
This seems terribly insecure to me.Aguila
I
1

Until you provide some code and the stacktrace, I'd suggest that you consider using a connection pool. If by "unknown database" you mean a database whose parameters are supplied by the end user, and hence no preconfigured connection pool is possible, you can still use the connection pool concept, rather than opening a new connection each time.

Also, theck this thread.

Indolent answered 20/12, 2009 at 8:54 Comment(0)
S
0

Session beans cannot be used concurrently, like skaffman said they were meant to handle state corresponding to the client session and are typically stored in the session object per client.

Refactoring to use a database pool to handle concurrent requests to your resources is the way to go.

In the meantime, if all you need is this trivial use, you could synchronise the call to constructReport as in:

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

Note that this is no solution if constructReport takes a significant amount of time relative to your number of clients.

Shangrila answered 20/12, 2009 at 9:48 Comment(0)
S
0

you should never syncronize servlet or ejb access since this cause requests queue and if you have N concurrently users the last one will wait for a long time and often get a timeout response!!! Syncronize method is not intended for this reason!!!

Sagittarius answered 14/7, 2011 at 7:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.