正确使用 Servlet 中的 Stateful Bean

发布于 2024-08-15 09:42:54 字数 991 浏览 3 评论 0原文

目前,我们有一个注入到 Servlet 中的 Stateful bean。问题是有时我们会得到一个 Caused by: javax.ejb.ConcurrentAccessException: SessionBean is moving another request。 [session-key: 7d90c02200a81f-752fe1cd-1] 在有状态 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();
        }
    } 
}

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

年少掌心 2024-08-22 09:42:54

有关 ConcurrentAccessException 的更多详细信息: EJB 规范,对 SLSB 的访问由应用程序同步。服务器。然而,SFS​​B 的情况并非如此。确保 SFSB 不会被同时访问的重担落在了应用程序开发人员的肩上。

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

这意味着从 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 节):

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

EJB 3.1 中已删除此类限制(第 4.3.13 节):

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

[...]

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

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

顾冷 2024-08-22 09:42:54

这不是有状态会话 Bean (SFSB) 的用途。它们旨在保存会话状态,并绑定到用户的 http 会话来保存该状态,就像直接在会话中存储状态的重量级替代方案一样。

如果您想保留数据库连接之类的东西,那么有更好的方法来实现。

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

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).

随风而去 2024-08-22 09:42:54

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

另外,请查看此线程

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.

雾里花 2024-08-22 09:42:54

会话 bean 不能同时使用,就像 skaffman 所说的那样,它们旨在处理与客户端会话相对应的状态,并且通常存储在每个客户端的会话对象中。

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

同时,如果您所需要的只是这种琐碎的使用,您可以同步对constructReport 的调用,如下所示:

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

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

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.

我很OK 2024-08-22 09:42:54

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文