Правильное использование Stateful Beans с сервлетами

В настоящее время у нас есть компонент Stateful, который вводится в сервлет. Проблема в том, что иногда мы получаем 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 строится из запроса, построенного на основе указанных параметров.

Причина, по которой мы решили использовать bean-компонент с сохранением состояния вместо bean-компонента без сохранения состояния, заключалась в том, что нам нужно открыть соединение с неизвестной базой данных и выполнить запросы к нему. С bean-компонентом без состояния кажется ужасно неэффективным многократно открывать и закрывать соединения с базой данных с каждым внедренным экземпляром bean-компонента.

5 ответов

Решение

Это не то, для чего предназначены сессионные компоненты с состоянием (SFSB). Они предназначены для хранения состояния диалога и должны быть привязаны к http-сеансу пользователя для хранения этого состояния, как тяжелая альтернатива прямому сохранению состояния в сеансе.

Если вы хотите хранить такие вещи, как соединения с базой данных, то есть лучшие способы сделать это.

Лучший вариант - использовать пул соединений. Вы всегда должны использовать пул соединений, и если вы работаете на сервере приложений (который, если вы используете EJB, то вы это делаете), то вы можете легко использовать конфигурацию источника данных вашего сервера приложений для создания пула соединений и использовать что внутри вашего сессионного компонента без состояния (SLSB).

Еще несколько подробностей, касающихся ConcurrentAccessException: согласно спецификации EJB, доступ к SLSB синхронизируется приложением. сервер. Однако это не относится к SFSB. Обязанность убедиться, что SFSB не доступен одновременно, лежит на плечах разработчика приложения.

Зачем? Ну, синхронизация SLSB необходима только на уровне экземпляра. То есть каждый конкретный экземпляр SLSB синхронизируется, но у вас может быть несколько экземпляров в пуле или на другом узле в кластере, и одновременные запросы в разных экземплярах не являются проблемой. К сожалению, с SFSB это не так просто из-за пассивации / активации экземпляров и репликации в кластере. Вот почему спецификация не обеспечивает это. Посмотрите на эту дискуссию, если вы заинтересованы в этой теме.

Это означает, что использовать SFSB из сервлета сложно. Пользователь с несколькими окнами из одного сеанса или перезагрузка страницы до завершения рендеринга может привести к одновременному доступу. Каждый доступ к EJB, который выполняется в сервлете, должен быть теоретически синхронизирован на самом бине. Что я сделал, чтобы создать 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). Вы также можете написать обычный класс-оболочку, который синхронизирует все методы компонента.

Тем не менее мой вывод: используйте SLSB, когда это возможно.

РЕДАКТИРОВАТЬ:

Этот ответ отражает спецификации EJB 3.0 (раздел 4.3.13):

Клиенты не могут совершать одновременные вызовы объекта сеанса с сохранением состояния. Если вызываемый клиентом бизнес-метод выполняется в экземпляре, когда другой вызываемый клиентом вызов, от того же или другого клиента, прибывает в тот же экземпляр класса сессионного компонента с сохранением состояния, если второй клиент является клиентом бизнеса компонента. интерфейс, одновременный вызов может привести к тому, что второй клиент получит исключение javax.ejb.ConcurrentAccessException

Такие ограничения были удалены в EJB 3.1 (раздел 4.3.13):

По умолчанию клиентам разрешено выполнять одновременные вызовы объекта сеанса с сохранением состояния, а контейнер необходим для сериализации таких одновременных запросов.

[...]

Разработчик компонента может дополнительно указать, что одновременные запросы клиентов к компоненту сеанса с состоянием запрещены. Это делается с использованием элемента дескриптора развертывания аннотации @AccessTimeout или элемента access-timeout со значением 0. В этом случае, если вызываемый клиентом бизнес-метод выполняется в экземпляре, когда другой вызванный клиентом вызов, из того же или другого клиент приходит к тому же экземпляру сессионного компонента с сохранением состояния, если второй клиент является клиентом бизнес-интерфейса компонента или представления без интерфейса, одновременный вызов должен привести к тому, что второй клиент получит исключение javax.ejb.ConcurrentAccessException

Пока вы не предоставите некоторый код и трассировку стека, я бы посоветовал вам рассмотреть возможность использования пула соединений. Если под "неизвестной базой данных" вы подразумеваете базу данных, параметры которой предоставляются конечным пользователем, и, следовательно, предварительно настроенный пул соединений невозможен, вы все равно можете использовать концепцию пула соединений, вместо того чтобы каждый раз открывать новое соединение.

Кроме того, Theck эта тема.

Вы никогда не должны синхронизировать доступ сервлетов или ejb, так как это вызывает очередь запросов, и если у вас одновременно N пользователей, последний будет ждать долго и часто получит ответ тайм-аута!!! Метод Syncronize не предназначен по этой причине!!!

Сессионные компоненты не могут использоваться одновременно, как сказал Скаффман, они предназначены для обработки состояния, соответствующего сеансу клиента, и обычно хранятся в объекте сеанса для каждого клиента.

Рефакторинг для использования пула базы данных для обработки одновременных запросов к вашим ресурсам - путь.

Между тем, если все, что вам нужно, это тривиальное использование, вы можете синхронизировать вызов constructReport следующим образом:

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

Обратите внимание, что это не решение, если constructReport отнимает значительное количество времени по сравнению с вашим числом клиентов.

Другие вопросы по тегам