1

I have a Spring MVC application, and I am trying to configure JPA (Hibernate implementation).

The configuration that I currently have allows me to execute JPQL SELECT queries. However, when I try to use entityManager.persist() or entityManager.merge() no changes are saved to the DB, no exception is thrown, and no errors/warnings appear in the Hibernate log. I suspect it has something to do with declarative transactions.

Previously, I have done some experiments with Hibernate native API + programmatic transactions, and I was able to save/update objects of the same entity with no problem.

What am I doing wrong?

My test class

@Repository
public class ProductsService {

    @PersistenceContext
    EntityManager entityManager;

    @Transactional
    public void createProduct() {

        Product p = new Product();
        p.setId(20);
        p.setName("test");
        entityManager.persist(p);
    }
}

Spring configuration

<beans ...>

    <bean id="dataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/MY_DB" />
        <property name="username" value="test" />
        <property name="password" value="test" />
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="net.myproject" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <bean id="persistenceExceptionTranslationPostProcessor" class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

    <tx:annotation-driven />

</beans>

Stack trace for the experiment proposed in one of the comments

at net.myproject.services.ProductsService.createProduct(ProductsService.java:34)
at net.myproject.web.rest.ProductsController.createProduct(ProductsController.java:56)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:690)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.filters.ExpiresFilter.doFilter(ExpiresFilter.java:1179)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
Alex
  • 883
  • 4
  • 11
  • 23
  • Do you see an insert SQL query being printed to the logs? Try throwing an exception from your method, and look at the stack trace. Is there a spring transaction interceptor in the stack trace? – JB Nizet Aug 17 '14 at 12:02
  • @JBNizet Thanks for your reply! `INSERT` SQL queries are not printed in the log (though when I execute `SELECT` queries they are in the log). I made the experiment with the exception. I am not sure where I should look for Spring transaction interceptor in the stack trace. However there is nothing like that in the stack trace immediately before the `createProduct()` method. (Is it where the interceptor should appear?) – Alex Aug 17 '14 at 12:27
  • Do you use this config? – Xstian Aug 17 '14 at 12:30
  • Post the stack trace. – JB Nizet Aug 17 '14 at 12:35
  • @Xstian I have just added `` line to my config, and tried again. It did not help. – Alex Aug 17 '14 at 12:39
  • @JBNizet I have posted the stack trace. – Alex Aug 17 '14 at 12:43
  • 3
    There's indeed no transaction interceptor in the stack trace. My guess is that you have a child context for your Spring-MVC dispatcher servlet that also has the ProductService bean, but which doesn't define any transactional stuff. Make sure the child context only defines/scans beans that are MVC-related (controllers), and doesn't define/scan the same beans as the root context. Or use a single context, which is much easier. – JB Nizet Aug 17 '14 at 12:47
  • @JBNizet I have `` tag in my Spring MVC config. It covers the `net.myproject.services.ProductsService` class. However, when I define the package more precisely (``), I cannot autowire the `ProductsService` class in my web application. So I have not been able to test your theory yet. – Alex Aug 17 '14 at 13:36
  • Can you show the configuration for Product? – Maarten Winkels Aug 17 '14 at 16:49
  • @Alex that's probably because the root context doesn't scan the services package. – JB Nizet Aug 17 '14 at 17:43
  • @JBNizet Now it works! Thank you! Now I have two different `` tags: one in the Spring MVC child context (it covers MVC controllers), and another in the root context (it covers the services layer) ([this](http://stackoverflow.com/a/9227900/3827696) answer explains the difference between a child and the root application contexts). The transaction interceptor appears in the stack trace, and the `merge()` method works. I still have a problem with `update()`, but it's a different story. I would accept your answer, if you posted it as an answer. – Alex Aug 18 '14 at 04:32
  • @Alex: I've written an answer which summarizes the problem and the solution. – JB Nizet Aug 18 '14 at 05:56

1 Answers1

2

After investigation, it appears that although transactional handling was correctly set up, it was done in the root context, whereas the Spring beans supposed to use this transactional handling (i.e. the ProductService), were scanned by a child context dedicated to Spring MVC.

Solution: have a

 <context:component-scan base-package="..." />

in the root context, to scan the service beans (and NOT the MVC controllers), and another

 <context:component-scan base-package="..." />

in the child context used to the Spring MVC dispacther servlet, to scan the MVC controllers, and NOT the transactional services.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • For those who will come here later, [here](http://stackoverflow.com/a/9227900/3827696) is an explanation of the difference between a child and the root application contexts. – Alex Aug 18 '14 at 06:56