Транзакции не начинаются в JSF @ViewScoped @Stateless bean

У меня есть веб-приложение на основе JSF 2 @ViewScoped, и я не могу заставить транзакции работать правильно, или, скорее: они вообще не запускаются.

Я также использую CDI и EJB3 Java EE 6.

Вот основная фасоль:

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.inject.Inject;
...

@ManagedBean
@ViewScoped
@Stateless
public class PqManager implements Serializable
{
    private List<PqListItem> pqItems;

    @Inject
    private PqService pqService;

    public List<PqListItem> getPqItems()
    {
        if ( pqItems == null )
        {
            pqItems = pqService.findActivePqs();
        }

        return pqItems;
    }

    ...
}

Компонент с областью представления используется на странице JSF для отображения простого списка в таблице данных. Он был сделан с областью просмотра, потому что в нем есть операции на основе AJAX для добавления элементов, удаления элементов и их сортировки с помощью RichFaces (фильтрация).

Я добавил @Stateless для каждого вызова метода, чтобы начать транзакцию (или создать новую, если таковой не существует, по умолчанию TransactionAttributeType.REQUIRED). Эта идея была взята из книги Core JavaServer Faces, 3-е изд., однако я не нашел ни одного примера, который соответствовал бы моему собственному.

Внедренный класс PqService (вместо него не имеет значения использование @EJB):

@Stateless
public class PqService extends JpaCrudService
{
    ...

    public List<PqListItem> findActivePqs()
    {
        return em.createQuery("SELECT NEW ... whatever not interesting here... WHERE pq.workflow = '" + Workflow.ACTIVE + "' GROUP BY pq.id", PqListItem.class).getResultList();
    }

    ...
}

JpaCrudService (в основном взято из примера Адама Биена http://www.adam-bien.com/roller/abien/entry/generic_crud_service_aka_dao):

//@Stateless
//@Local(CrudService.class)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public abstract class JpaCrudService implements CrudService
{
    @PersistenceContext(unitName = "PqGeneratorPu")
    protected EntityManager em;

    @Override
    public <T> T create(T t)
    {
        em.persist(t);
        em.flush();
        em.refresh(t);

        return t;
    }

    ...
}

Единственная разница в том, что я подкласс JpaCrudService, потому что мне не нравятся запросы, хранящиеся в/в объектах. Поэтому я пропустил аннотацию @Local (поправьте меня, если это неправильно). @Stateless не унаследован AFAIK, и я только ввожу подклассы, поэтому я также прокомментировал это.

При этом доступ к bean-компоненту осуществляется со страницы JSF:

  <rich:dataTable value="#{pqManager.pqItems}"
                  var="pq">
    <f:facet name="header">
      <h:outputText value="Active" />
    </f:facet>
    ...

Однако при загрузке страницы я получаю исключение:

javax.ejb.EJBTransactionRequiredException: Transaction is required for invocation: org.jboss.invocation.InterceptorContext@7a6c1c92
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.mandatory(CMTTxInterceptor.java:255)
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:184)
    at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
    at org.jboss.as.ejb3.component.interceptors.CurrentInvocationContextInterceptor.processInvocation(CurrentInvocationContextInterceptor.java:41)
    at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
    at org.jboss.as.ejb3.component.interceptors.LoggingInterceptor.processInvocation(LoggingInterceptor.java:59)
    at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
    at org.jboss.as.ee.component.NamespaceContextInterceptor.processInvocation(NamespaceContextInterceptor.java:50)
    at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
    at org.jboss.as.ee.component.TCCLInterceptor.processInvocation(TCCLInterceptor.java:45)
    at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
    at org.jboss.invocation.ChainedInterceptor.processInvocation(ChainedInterceptor.java:61)
    at org.jboss.as.ee.component.ViewService$View.invoke(ViewService.java:165)
    at org.jboss.as.ee.component.ViewDescription$1.processInvocation(ViewDescription.java:173)
    at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288)
    at org.jboss.invocation.ChainedInterceptor.processInvocation(ChainedInterceptor.java:61)
    at org.jboss.as.ee.component.ProxyInvocationHandler.invoke(ProxyInvocationHandler.java:72)
    at de.company.webapp.service.PqService$$$view95.findActivePqsFor(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.jboss.weld.util.reflection.SecureReflections$13.work(SecureReflections.java:264)
    at org.jboss.weld.util.reflection.SecureReflectionAccess.run(SecureReflectionAccess.java:52)
    at org.jboss.weld.util.reflection.SecureReflectionAccess.runAsInvocation(SecureReflectionAccess.java:137)
    at org.jboss.weld.util.reflection.SecureReflections.invoke(SecureReflections.java:260)
    at org.jboss.weld.bean.proxy.EnterpriseBeanProxyMethodHandler.invoke(EnterpriseBeanProxyMethodHandler.java:111)
    at org.jboss.weld.bean.proxy.EnterpriseTargetBeanInstance.invoke(EnterpriseTargetBeanInstance.java:56)
    at org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:105)
    at de.company.webapp.service.PqService$Proxy$_$$_Weld$Proxy$.findActivePqs(PqService$Proxy$_$$_Weld$Proxy$.java)
    at de.company.webapp.facade.PqManager.getPqItems(PqManager.java:84)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    .
    .
    .

Это не удается, потому что вызов pqService.findActivePqsFor() не выполняется в существующей транзакции (TransactionAttributeType.MANDATORY, которая унаследована, насколько я знаю).

Обратите внимание, что страница отображается правильно без использования транзакций путем удаления TransactionAttributeType.MANDATORY на JpaCrudService и использования расширенного менеджера сущностей, но это было только для целей тестирования.

Но почему это не работает? Почему транзакция не начинается здесь? Есть ли что-нибудь с bean-компонентом JSF @ViewScoped? Несовместимо?

Как это исправить?

PS: я использую JBoss AS 7.1.1.


person Kawu    schedule 21.04.2012    source источник


Ответы (1)


Если вы используете CDI, удалите аннотации JSF. Аннотации JSF не управляют EJB так, как CDI. Вероятно, вы путаете контейнер с используемыми аннотациями. Вы также можете использовать MyFaces CODI для некоторых расширений или попробовать воссоздать ViewScope с помощью CDI. В сети есть несколько примеров.

person LightGuard    schedule 21.04.2012
comment
Спасибо. Является ли Seam 3 Faces решением, которое я ищу? Наконец, мне интересно, что другие люди делают, чтобы получить транзакцию от такого bean-компонента @ViewScoped в простой Java EE 6 без CDI. Как это будет работать/выглядеть? - person Kawu; 22.04.2012
comment
Да, это сработало бы, однако Seam 3 активно не разрабатывается. В DeltaSpike идет работа, но мы еще не занимались JSF. Что касается другого вопроса, вы должны выполнить поиск JNDI для UserTransaction и вручную обработать границы транзакции. - person LightGuard; 23.04.2012
comment
Ну, а пока мне просто нужно решение, которое запускало бы транзакции вообще. Я буду следить за DeltaSpike, см. infoq.com/news/ 2012/04/шов-дельтаспайк. Спасибо за эту топовую информацию! - person Kawu; 23.04.2012