2015-06-23 32 views
9

uwzględniając następujące, dość prosty kod i poprawnie skonfigurowane JTA opartego kontekst trwałości:@Transactional ignorowane w klasie bazowej CDI Fasoli

abstract class AbstractRepository<E> { 
    @PersistenceContext 
    protected EntityManager em; 

    @Transactional 
    public synchronized void persist(E entity) { 
     em.persist(entity); 
     em.flush(); 
    } 
} 

@ApplicationScoped 
class MyEntityRepository extends AbstractRepository<MyEntity> { 

} 

jestem napotyka następujący wyjątek podczas wywoływania MyEntityRepository.persist():

2015-06-23T12:34:55.233+0200|Severe: javax.persistence.TransactionRequiredException 
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.doTxRequiredCheck(EntityManagerWrapper.java:161) 
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.doTransactionScopedTxCheck(EntityManagerWrapper.java:151) 
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.persist(EntityManagerWrapper.java:281) 
    at my.project.AbstractRepository.persist(AbstractRepository.java:28) 
    at my.project.QuestionnaireRepository.persist(QuestionnaireRepository.java:1) 
    at my.project.QuestionnaireRepository$Proxy$_$$_WeldClientProxy.persist(Unknown Source) 
    at my.project.QuestionnaireForm.save(QuestionnaireForm.java:29) 
    at my.project.QuestionnaireForm.lambda$0(QuestionnaireForm.java:1) 
    at my.project.QuestionnaireForm$$Lambda$56/1079229220.buttonClick(Unknown Source) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:483) 
    at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:508) 
    at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:198) 
    at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:161) 
    at com.vaadin.server.AbstractClientConnector.fireEvent(AbstractClientConnector.java:977) 
    at com.vaadin.ui.Button.fireClick(Button.java:393) 
    at com.vaadin.ui.Button$1.click(Button.java:61) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:483) 
    at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:168) 
    at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:118) 
    at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:291) 
    at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:184) 
    at com.vaadin.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:92) 
    at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41) 
    at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1408) 
    at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:350) 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) 
    at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1682) 
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:344) 
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214) 
    at org.glassfish.tyrus.servlet.TyrusServletFilter.doFilter(TyrusServletFilter.java:295) 
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256) 
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214) 
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:316) 
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:160) 
    at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:734) 
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:673) 
    at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99) 
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174) 
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:415) 
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:282) 
    at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:459) 
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:167) 
    at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:201) 
    at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:175) 
    at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:235) 
    at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119) 
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:284) 
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:201) 
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:133) 
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:112) 
    at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77) 
    at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:561) 
    at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:112) 
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:117) 
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:56) 
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:137) 
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:565) 
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:545) 
    at java.lang.Thread.run(Thread.java:745) 

W Aby to naprawić, muszę dodać:

@Override 
@Transactional 
public void persist(Entity e) { 
    super.persist(e); 
} 

Wh w może spowodować taki wyjątek? Adnotacja @Transactional jest oznaczona jako @Inherited.

+0

[CDI nie obsługują transakcji, z wyjątkiem korzystania z Java EE 7] (http://stackoverflow.com/questions/17838221/jee7-do-ejb-and-cdi-beans-support-container-managed-transactions) –

+0

Używam Glassfish4 i JEE7, więc powinienem być obsługiwany (JTA 1.2), a kiedy deklaruję '@ Transactional' w klasie koncentratu ** działa **. – Crozin

+0

Czy możesz udostępnić swój 'beans.xml'? –

Odpowiedz

5

Jest tak, ponieważ kontener nie będzie widzieć adnotacji w metodach lokalnych: w AbstractRepository metoda jest opisana, ale jest lokalna w stosunku do MyEntityRepository. To sprawia, że ​​jest niewidoczny dla Glassfish, który nie inicjuje żadnej transakcji, stąd twój wyjątek.

QuestionnaireRepository powinien wstrzyknąć MyEntityRepository i powinien zainicjować samą transakcję.

Wniosek o przeniesieniu do domu to: @Transactional adnotacje są pobierane tylko metodami biznesowymi, które są publicznymi metodami określanymi przez klasę "injector".

Zobacz także an example in which the method is in the same instance

Myślałam o tym, jak można zrobić to samo bez wdrożenia persist w konkretnej klasy, ale nie sądzę, że to możliwe, ponieważ pojemnik musiałby instancję klasy abstrakcyjnej (co jest niedorzeczne, ponieważ nie mają konstruktora, który mógłby wywołać poprzez odbicie).

Może mógłbyś zrobić coś z Java 8 i domyślnymi metodami interfejsu, które są dodawane jako zachowanie domyślne w konkretnej klasie.

// UNTESTED! 
public interface Repository<E> { 
    @Transactional 
    synchronized default void persist(E entity) { 
     em.persist(entity); 
    } 
} 
+0

Mam zamiar mieć kilkanaście takich repozytoriów z prawie identyczną implementacją (dla kilku metod). Stworzenie wiązki metod proxy w każdym z nich jest możliwe, ale wydaje się, że jest to niewłaściwy sposób i naruszenie DRY. – Crozin

+1

@Crozin Dlaczego tak, powinieneś unikać takiej struktury. Repozytoria powinny być używane tylko w przypadku znaczących danych zagregowanych.W systemie powinno być kilka agregatów i najprawdopodobniej mają one zupełnie inną implementację, więc nie ma potrzeby używania klasy abstrakcyjnej. – gurghet

+3

Ponadto transakcja powinna zostać zainicjowana na poziomie aplikacji, a nie na poziomie infrastruktury. Więc nie powinieneś w ogóle używać @Transactional w repozytoriach – gurghet

1

Dziękujemy za dodanie pliku beans.xml. Jeśli miałbym zgadywać, GF4 cierpi na błąd CDI 1.1, w którym przechwytywacze nie definiują komponentu bean, a w aplikacji jest używany niewłaściwy tryb wykrywania. Jeśli zmienisz plik beans.xml do korzystania

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
     http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" 
     bean-discovery-mode="all"> 
</beans> 

To zmusi go znaleźć wszystkie ziarna. Problem polega na tym, że twoja klasa podstawowa nie jest wykrywana iw rezultacie nie ma stanu transakcji zastosowanego do tej metody.

+0

Zmieniłem 'beans.xml', ale nic nie zmienia, wciąż otrzymuję' javax.persistence.TransactionRequiredException'. Mogłem przejść na EJB, ale chciałem spróbować CDI. – Crozin