使用Servlet正确使用有状态Bean

时间:2009-12-20 07:53:43

标签: java servlets java-ee ejb stateful

我们目前有一个注入Servlet的有状态bean。问题是有时我们在有状态bean上执行方法时得到Caused by: javax.ejb.ConcurrentAccessException: SessionBean is executing another request. [session-key: 7d90c02200a81f-752fe1cd-1]

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

在上面的代码中,constructReport将检查是否需要打开与报表中指定的数据库的新连接,之后在根据指定参数构建的查询中构建HTML报表。 / p>

我们选择在无状态bean上使用有状态bean的原因是因为我们需要打开与未知数据库的数据库连接并对其执行查询。对于无状态bean,使用每个注入的bean实例重复打开和关闭数据库连接似乎非常低效。

5 个答案:

答案 0 :(得分:14)

关于ConcurrentAccessException的更多细节:根据EJB规范,应用程序同步对SLSB的访问。服务器。但是,SFSB并非如此。确保不同时访问SFSB的负担在应用程序开发人员的肩上。

为什么呢?好吧,只有在实例级别才需要同步SLSB。也就是说,SLSB的每个特定实例都是同步的,但是您可能在池中或群集中的不同节点上有多个实例,并且不同实例上的并发请求不是问题。遗憾的是,由于实例的钝化/激活以及跨群集的复制,这对于SFSB来说并不那么容易。这就是规范没有强制执行此操作的原因。如果您对该主题感兴趣,请查看this dicussion

这意味着从servlet使用SFSB很复杂。具有来自同一会话的多个窗口的用户,或者在渲染完成之前重新加载页面可以导致并发访问。理论上,对servlet中完成的EJB的每次访问都需要在bean本身上进行同步。我所做的是创建一个InvocationHandler来同步特定EJB实例上的所有调用:

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

}

然后,在获得EJB的远程引用之后,用SynchronizationHandler包装它。这样您就可以确保不会从您的应用程序同时访问此特定实例(只要它只在一个JVM中运行)。您还可以编写一个常规包装类来同步bean的所有方法。

然而,我的结论是:尽可能使用SLSB。

修改

这个答案反映了EJB 3.0规范(第4.3.13节):

  

不允许客户端对有状态会话进行并发调用   宾语。如果客户端调用的业务方法正在进行中   实例当另一个客户端调用的调用,来自相同或不同   client,到达有状态会话bean类的同一个实例,   如果第二个客户端是bean的业务接口的客户端,那么   并发调用可能导致第二个客户端接收到   javax.ejb.ConcurrentAccessException

这些限制已在EJB 3.1(第4.3.13节)中删除:

  

默认情况下,允许客户端对有状态进行并发调用   会话对象和容器需要序列化这样的   并发请求。

     

[...]

     

Bean Developer可以选择指定并发客户端   禁止向有状态会话bean发出请求。这是使用完成的   @AccessTimeout批注或访问超时部署描述符   值为0的元素。在这种情况下,如果是客户端调用的业务   当另一个客户端调用的调用时,方法正在实例上,   来自相同或不同的客户端,到达同一个实例   有状态会话bean,如果第二个客户端是bean的客户端   业务接口或无接口视图,并发调用   必须导致第二个客户接收到   javax.ejb.ConcurrentAccessException

答案 1 :(得分:9)

这不是有意使用的有状态会话bean(SFSB)。它们被设计为保持对话状态,并且被绑定到用户的http会话以保持该状态,就像直接在会话中存储状态的重量级替代。

如果你想保留数据库连接之类的东西,那么有更好的方法可以解决它。

最佳选择是使用连接池。您应该始终使用连接池,如果您在应用程序服务器内运行(如果您正在使用EJB,那么您就是这样),那么您可以轻松地使用您的appserver的数据源配置创建一个连接池,并在无状态会话bean(SLSB)中使用它。

答案 2 :(得分:1)

在您提供一些代码和堆栈跟踪之前,我建议您考虑使用connection pool。 如果“未知数据库”是指最终用户提供参数的数据库,因此无法预先配置连接池,您仍然可以使用连接池概念,而不是每次都打开新连接。

另外,theck this thread

答案 3 :(得分:0)

会话bean不能同时使用,例如skaffman说它们意味着处理与客户端会话相对应的状态,并且通常存储在每个客户端的会话对象中。

重构使用数据库池来处理对资源的并发请求是可行的方法。

与此同时,如果你需要的只是这个简单的用途,你可以将调用同步到constructReport,如下所示:

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

请注意,如果constructReport相对于您的客户端数量需要相当长的时间,则这不是解决方案。

答案 4 :(得分:0)

你永远不应该同步servlet或ejb访问,因为这会导致请求队列,如果你有N个并发用户,那么最后一个会等待很长时间并经常得到超时响应! Syncronize方法不是出于这个原因!!!