Правильное использование Stateful Beans с сервлетами - PullRequest
8 голосов
/ 20 декабря 2009

В настоящее время у нас есть компонент 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 ]

14 голосов
/ 20 декабря 2009

Еще несколько подробностей относительно 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, когда это возможно.

EDIT

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

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

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

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

[...]

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

9 голосов
/ 20 декабря 2009

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

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

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

1 голос
/ 20 декабря 2009

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

Также theck эта тема .

0 голосов
/ 14 июля 2011

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

0 голосов
/ 20 декабря 2009

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...