Jorge Cespedes
Jorge Cespedes

Reputation: 597

Spring 3.2.1 + hibernate-core 4.2.0: merge() not working

I'm starting to learn Spring for web development using springapp example. Now, I'm trying to increase the price of every product in a list (retrieved from a database) and then save back to the DB with the new prices. It can retrieve the product list but merge doesn't do anything. I added .flush() to the code and it gave an ugly exception (just as soon as the application starts).

This is the exception thrown as soon as application starts (before invoking method calling .flush()):

javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:993)
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.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:366)
at $Proxy18.flush(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.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:241)
at $Proxy18.flush(Unknown Source)
at org.springtest.mavenspringapp.repository.JPAProductDao.saveProduct(JPAProductDao.java:34)
at org.springtest.mavenspringapp.service.SimpleProductManager.increasePrice(SimpleProductManager.java:35)
at org.springtest.mavenspringapp.web.PriceIncreaseFormController.onSubmit(PriceIncreaseFormController.java:38)
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.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
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:745)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:920)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:827)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:801)
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.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
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:100)
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:1041)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

This is once I increase each product's price and invoke .merge() followed by .flush():

javax.persistence.TransactionRequiredException: no transaction is in progress
org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:993)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.lang.reflect.Method.invoke(Unknown Source)
org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:366)
$Proxy18.flush(Unknown Source)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.lang.reflect.Method.invoke(Unknown Source)
org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:241)
$Proxy18.flush(Unknown Source)
org.springtest.mavenspringapp.repository.JPAProductDao.saveProduct(JPAProductDao.java:34)
org.springtest.mavenspringapp.service.SimpleProductManager.increasePrice(SimpleProductManager.java:35)
org.springtest.mavenspringapp.web.PriceIncreaseFormController.onSubmit(PriceIncreaseFormController.java:38)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.lang.reflect.Method.invoke(Unknown Source)
org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:920)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:827)
javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:801)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)

This is ProductDao:

@Repository(value = "productDao")
public class JPAProductDao implements ProductDao {

    private EntityManager em = null;

    /*
     * Sets the entity manager.
     */
    @PersistenceContext
    public void setEntityManager(EntityManager em) {
        this.em = em;
    }

    @Transactional(readOnly = true)
    @SuppressWarnings("unchecked")
    public List<Product> getProductList() {
        return em.createQuery("select p from Product p").getResultList();
    }

    @Transactional(readOnly = false)
    public void saveProduct(Product prod) {
        em.merge(prod);
        em.flush();
    }

}

This is a method in my controller processing the price increase form:

@RequestMapping(method = RequestMethod.POST)
public String onSubmit(@Valid PriceIncrease priceIncrease, BindingResult result)
{
    if (result.hasErrors())
    {
        return "priceincrease";
    }

    int increase = priceIncrease.getPercentage();
    logger.info("Increasing prices by " + increase + "%.");

    productManager.increasePrice(increase);

    return "redirect:/hello.htm";
}

ProductManager is a service which is implemented by a class named SimpleProductManager. This class updates each product's price and attempts to save it to the DB through JpaProductDao.

Here's my applicationContext.xml:

    <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- holding properties for database connectivity /-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- enabling annotation driven configuration /-->
    <context:annotation-config/>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="${jdbc.driverClassName}"/>
      <property name="url" value="${jdbc.url}"/>
      <property name="username"  value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    p:dataSource-ref="dataSource"
    p:jpaVendorAdapter-ref="jpaAdapter">
    <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
    </property>                             
    <property name="persistenceUnitName" value="springappPU"></property>
    </bean>

    <bean id="jpaAdapter"
    class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
    p:database="${jpa.database}"
    p:showSql="${jpa.showSql}"/>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
    p:entityManagerFactory-ref="entityManagerFactory"/>

    <!-- <tx:annotation-driven transaction-manager="transactionManager"/>-->
    <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>

    <context:component-scan base-package="org.springtest.mavenspringapp">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
    <!-- Scans the classpath of this application for @Components to deploy as beans 
    <context:component-scan base-package="org.springtest.mavenspringapp.repository" />
    <context:component-scan base-package="org.springtest.mavenspringapp.service" />
    <context:component-scan base-package="org.springtest.mavenspringapp.web" />-->

</beans>

BTW, I added the .flush method call because someone suggested it here (it was another question about not persisting changes).

Almost forgot, if .flush() is not included products are not updated and not exception is thrown. This is hibernate log output:

Hibernate: select product0_.id as id1_0_0_, product0_.description as descript2_0_0_, product0_.price as price3_0_0_ from products product0_ where product0_.id=?
Hibernate: select product0_.id as id1_0_0_, product0_.description as descript2_0_0_, product0_.price as price3_0_0_ from products product0_ where product0_.id=?
Hibernate: select product0_.id as id1_0_, product0_.description as descript2_0_, product0_.price as price3_0_ from products product0_

I hope you can help me. Thanks in advanced.

EDIT: I'm posting my web.xml file content:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/applicationContext.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <display-name>Mavenspringapp</display-name>

    <servlet>
        <servlet-name>mavenspringapp</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/app-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mavenspringapp</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>
</web-app>

app-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/mvc 
                        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="messages"/>
    </bean>

    <!-- Scans the classpath of this application for @Components to deploy as beans -->
    <context:component-scan base-package="org.springtest.mavenspringapp" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>


    <!-- Configures the @Controller programming model -->
    <mvc:annotation-driven/>
    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView">
        </property>
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>

Upvotes: 0

Views: 1152

Answers (3)

M. Deinum
M. Deinum

Reputation: 124526

Judging from your stack trace you are loading your beans twice, once by the ContextLoaderListener which has correctly transactions defined. And once in the DispatcherServlet without transactions. (Your stack trace doesn't show a TransactionInterceptor which leads me to believe this).

Make sure that you don't scan for the same components twice (don't duplicate the <context:component-scan /> elements in both the configuration files.

The problem is the following line in your app-config.xml

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

This again creates new instances of all your beans and doesn't apply transactions to it. AOP is only applied in the same application context the beans are defined in. So basically you have 2 instances of your JPAProductDao one (which sits there useless) with transactions and what not applied in the ApplicationContext loaded by the ContextLoaderListener and another one (no transactions applied) in the ApplicationContext loaded by your DispatcherServlet.

Modify the component-scanning your applicationContext.xml to the following

<context:component-scan base-package="org.springtest.mavenspringapp">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

and in your app-config.xml to the following (note: don't forget the use-default-filters attribute!!!!)

<context:component-scan base-package="org.springtest.mavenspringapp" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

You can also remove the <context:annotation-config /> that is already implied by the use of <context:component-scan />. Saves you another line of xml.

Upvotes: 1

Markus Coetzee
Markus Coetzee

Reputation: 3444

You haven't got a transaction as the exception suggests: javax.persistence.TransactionRequiredException: no transaction is in progress

You need an overarching transaction as the object you pass to your saveProduct method has been detached (it's no longer managed by the entity manager).

Annotate your controller with @Transactional and you should be good. Remember after you fetch your object they still need to be managed in order to update them. Also remove the readOnly = true when fetching the objects. Furthermore, you don't actually need the @Transactional annotation on the repository as in all cases I can think of you would need an overarching transaction, and you don't need a transaction to read from a datasource.

Upvotes: 1

kyla
kyla

Reputation: 396

try:

@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)

documentation here

Upvotes: 0

Related Questions