Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@Transactional ignored in CDI Bean's base class

Having following, fairly simple code and correctly configured JTA-based persistence context:

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> {

}

I am encountering following exception while invoking 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)

In order to fix it I have to add:

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

What could cause such exception? @Transactional annotation is marked as @Inherited.

like image 347
Crozin Avatar asked Jun 23 '15 10:06

Crozin


2 Answers

This is because the container won’t see annotation in local methods: in AbstractRepository the method is annotated but is local relative to MyEntityRepository. This makes it invisible to Glassfish which won’t initiate any transaction, hence your exception.

QuestionnaireRepository should inject MyEntityRepository and should initiate the transaction itself.

The conclusion to bring at home is: @Transactional annotations are picked up only on business methods, which are public methods called by the ‘injector’ class.

See also an example in which the method is in the same instance

I was thinking about how you could do the same thing without implementing persist in the concrete class but I don’t think it’s possible because the container would have to instantiate an abstract class (which is preposterous as they have no constructor to call via reflection).

Maybe you could do something with Java 8 and interface default methods, which are added as default behavior in a concrete class.

// UNTESTED!
public interface Repository<E> {
    @Transactional
    synchronized default void persist(E entity) {
        em.persist(entity);
    }
}
like image 149
gurghet Avatar answered Nov 12 '22 17:11

gurghet


Thank you for adding your beans.xml file. If I had to guess, GF4 suffers from a CDI 1.1 bug where interceptors aren't bean defining and the wrong discovery mode is being used in your app. If you change your beans.xml file to use

<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>

This will force it to find all beans. The issue is that your base class isn't being discovered and as a result isn't having transaction state applied to the method.

like image 26
John Ament Avatar answered Nov 12 '22 17:11

John Ament