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

В настоящее время у нас есть stateful 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 строится из запроса, который строится из указанных параметров.

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

5 ответов


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

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

лучший вариант-использовать пул соединений. Вы должны всегда использовать подключение пул, и если вы работаете внутри сервера приложений (который, если вы используете EJBs, то вы), то вы можете легко использовать конфигурацию источника данных вашего appserver для создания пула соединений и использовать его внутри вашего Session bean (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):

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

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

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

[...]

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


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

кроме того, theck этой теме.


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

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

тем временем, если все, что вам нужно, это тривиальное использование, вы можете синхронизировать вызов constructReport как в:

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

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


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