Производитель компонента должен указать, будет ли MDB определять границы своих транзакций программно, или он будет рассчитывать на то, что от его имени это сделает EJB-контейнер. Производитель должен для этого указать значение bean или container в поле transaction-type в дескрипторе развертывания EJB-модуля соответствующего компонента, управляемого сообщениями.
Независимо от того, будут ли границы транзакции управляться компонентом или контейнером, компонент, управляемый сообщениями, может обращаться только к тому транзакционному контексту, в котором он работает, и при помощи соответствующих методов интерфейса MessageDrivenContext.
Интерфейс MessageDrivenContext
Интерфейс javax.ejb.MessageDrivenContext расширяет интерфейс javax.ejb.EJBContext. Однако в отличие от интерфейсов SessionContext и EntityContext, интерфейс MessageDrivenContext не определяет никаких дополнительных методов. Родительский интерфейс EJBContext показан в примере 8.17.
Пример 8.17. Интерфейс javax.ejb.EJBContext interface package javax.ejb;
import java.security.Identity; import java.security.Principal; import java.util.Properties;
import javax.transaction.UserTransaction;
public interface EJBContext
{
// Home-методы EJB
public abstract EJBHome getEJBHome();
public abstract EJBLocalHome getEJBLocalHome(); // Методы, связанные с безопасностью
public abstract Principal getCallerPrincipal();
public abstract boolean isCallerInRole(String s);
// Методы для работы с транзакцией
public abstract UserTransaction getUserTransaction() throws IllegalStateException;
public abstract void setRollbackOnly() throws IllegalStateException;
public abstract boolean getRollbackOnly() throws IllegalStateException;
// Методы служб таймера
public abstract TimerService getTimerService() throws IllegalStateException;
// Выводимые из употребления методы
public abstract Properties getEnvironment();
public abstract Identity getCallerIdentity();
public abstract boolean isCallerInRole(Identity identity); }
Примечание. Используя экземпляр компонента, управляемого сообщениями, вызывайте только методы работы с транзакцией и методы служб таймера, предоставляемые интерфейсом MessageDrivenContext.
Попытка вызвать home-методы EJB приведет к генерации исключения java.lang.
IllegalStateException, поскольку MDB-компоненты не определяют объектов EJBHome или EJBLocalHome.
Попытка вызвать метод getCallerPrincipal в версии 2.1 спецификации EJB считается
допустимой. Однако, в случае MDB-компонента вызывающей стороной будет EJB-контейнер, который не имеет контекста безопасности клиента. В этом случае метод getCallerPrincipal возвратит представление неаутентифицированного идентификатора. Вызов функции isCallerInRole считается спецификацией EJB недопустимым, и это приведет к генерации исключения java.lang.IllegalStateException.
Транзакции, управляемые контейнером
Говорят, что MDB-компонент, в свойстве transaction-type которого указано значение Container, использует контейнерно-управляемые транзакции. Когда MDB использует контейнерно-управляемые транзакции, EJB-контейнер использует атрибут транзакций для метода получателя запросов с целью определения тех действий, которые нужно предпринять, когда сообщение приходит в соответствующий пункт назначения.
Для метода слушателя сообщений можно задавать следующие атрибуты транзакций.
• NotSupported (не поддерживаются).
EJB-контейнер не создает транзакций перед получением сообщения из пункта назначения и вызовом метода получателя запроса — MDB-компонента. Следовательно, если MDB-компонент обращается к другим менеджерам ресурсов или EJB, он делает это без указания контекста транзакций.
Кроме того, если в ходе обработки возникает ошибка, сообщение, возможно, не получится поместить обратно в пункт назначения для повторной доставки (это зависит от возможностей JMS-провайдера).
• Required (обязательны).
EJB-контейнер создает транзакцию перед получением сообщения из пункта назначения и вызовом метода получателя запроса — MDB-компонента. Если MDB-компонент обращается к менеджеру ресурсов в методе слушателя сообщений, то этот доступ происходит в контексте данной транзакции. Точно также, если MDB-компонент вызывает другие EJB-компоненты в методе слушателя сообщений, то EJB-контейнер передает при вызове контекст транзакции. Когда метод слушателя сообщений завершает свою работу, EJB-контейнер пытается завершить транзакцию. В случае компонента, управляемого JMS-сообщениями, откат транзакции приводит к тому, что сообщение возвращается в пункт назначения для повторной доставки.
Если метод получателя запросов задает в качестве атрибута транзакций значение Required, он может использовать только методы getRollbackOnly и setRollbackOnly объекта MessageDrivenContext. Код, необходимый для того, чтобы пометить транзакцию для отката в методе слушателя сообщений, показан в примере 8.18.
Пример 8.18. Использование метода setRollbackOnly
public class SampleMDBBean implements MessageDrivenBean, MessageListener {
private MessageDrivenContext msgDrivenCtx;
// Методы жизненного цикла убраны для простоты
public void onMessage(Message msg)
{
try {
// Обработка сообщения
// попытка получения доступа к реляционной базе данных
}
catch (SQLException e) {
// произошла ошибка, откат транзакции msgDrivenCtx.setRollbackOnly(); }
}
}
Транзакции, управляемые компонентом
Говорят, что MDB-компонент, в свойстве transaction-type которого указано значение Bean, использует компонентно-управляемые транзакции. Когда MDB использует компонентно-управляемые транзакции, EJB-контейнер не создает транзакцию перед по-
лучением сообщения из пункта назначения и вызовом метода получающего запрос MDB-компонента. Следовательно, в случае компонента, управляемого JMS-сообщениями, сообщение может не возвращаться в пункт назначения для повторной доставки, если в ходе обработки сообщения возникает ошибка. Метод получателя запроса отвечает за создание любых транзакций, необходимых для обработки сообщения.
MDB-компонент, применяющий компонентно-управляемые транзакции, может использовать только метод getUserTransaction объекта MessageDrivenContext. Затем он может использовать интерфейс javax.transaction.UserTransaction для того, чтобы начинать, фиксировать и откатывать транзакции. Код, использующий интерфейс UserTransaction внутри метода слушателя сообщений, показан в примере 8.19.
Пример 8.19. Использование интерфейса javax.transaction.UserTransaction
public class SampleMDBBean implements MessageDrivenBean, MessageListener {
private MessageDrivenContext msgDrivenCtx;
// Методы жизненного цикла убраны для простоты
public void onMessage(Message msg)
{
// Получение ссылки на объект UserTransaction
UserTransaction userTx = msgDrivenCtx.getUserTransaction();
try
{
// Начало транзакции userTx.begin();
// Обработка сообщения
// Попытка доступа к реляционной базе данных // Попытка зафиксировать транзакцию userTx.commit();
}
catch (Exception e) {
try {
// Произошла ошибка, откат транзакции userTx.rollback();
}
catch (SystemException e2) {
e2.printStackTrace();
}
}
}
}
Примечание. Из-за сложной природы распределенных транзакций рекомендуется, чтобы производители компонентов использовали контейнерно-управляемые транзакции.