9

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.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
Crozin
  • 43,890
  • 13
  • 88
  • 135
  • [CDIs do not support transactions, except you use Java EE 7](http://stackoverflow.com/questions/17838221/jee7-do-ejb-and-cdi-beans-support-container-managed-transactions) – V G Jun 23 '15 at 10:47
  • I am running on Glassfish4 and JEE7 so it should be supported (JTA 1.2), and when I declare `@Transactional` in concentrate class **it is** working. – Crozin Jun 23 '15 at 10:54
  • Can you share your `beans.xml`? – John Ament Jun 23 '15 at 14:54
  • It's an "empty" file: `` – Crozin Jun 23 '15 at 16:39

2 Answers2

6

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);
    }
}
Community
  • 1
  • 1
gurghet
  • 7,591
  • 4
  • 36
  • 63
  • I'm going to have over dozen of such repositories with almost identical implementation (for several methods). Creating bunch of proxy-methods in every one of them is possible but it seems like a wrong way and violation of DRY. – Crozin Jun 29 '15 at 06:23
  • 1
    @Crozin Why yes, you should avoid such a structure. Repositories should be used only for meaningful aggregates. There should be a handful of aggregates per system and they most likely have quite different implementation so there is no need to use an abstract class. – gurghet Jun 29 '15 at 21:36
  • 3
    Moreover, transaction should be initiated at an application level, not an infrastructure level. So you should not use @Transactional at all in repositories – gurghet Jun 30 '15 at 17:34
1

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.

John Ament
  • 11,595
  • 1
  • 36
  • 45
  • I've changed `beans.xml` but it doesn't change anything, I'm still getting `javax.persistence.TransactionRequiredException`. I could switch to EJB but I wanted to give a try to CDI. – Crozin Jun 29 '15 at 06:20