【问题标题】:Spring 3.2.1 + hibernate-core 4.2.0: merge() not workingSpring 3.2.1 + hibernate-core 4.2.0:merge() 不工作
【发布时间】:2013-11-19 18:07:01
【问题描述】:

我开始使用 springapp 示例学习 Spring 进行 Web 开发。现在,我正在尝试提高列表中每个产品的价格(从数据库中检索),然后将新价格保存回数据库。它可以检索产品列表,但合并不做任何事情。我将 .flush() 添加到代码中,它给出了一个丑陋的异常(就在应用程序启动时)。

这是应用程序启动时抛出的异常(在调用方法调用 .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)

这是一次我提高每个产品的价格并调用 .merge() 后跟 .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)

这是 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();
    }

}

这是我的控制器中处理涨价表单的方法:

@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 是由一个名为 SimpleProductManager 的类实现的服务。此类更新每个产品的价格并尝试通过 JpaProductDao 将其保存到数据库中。

这是我的 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>

顺便说一句,我添加了 .flush 方法调用,因为有人在这里建议它(这是关于不持久更改的另一个问题)。

差点忘了,如果不包含 .flush() ,产品不会更新,也不会抛出异常。这是休眠日志输出:

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_

我希望你能帮助我。提前致谢。

编辑:我正在发布我的 web.xml 文件内容:

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

【问题讨论】:

  • 请发布您的 web.xml。
  • @M.Deinum 我编辑了我的问题,现在包含了我的 web.xml 文件内容
  • 能否也添加app-config.xml
  • 还有你的问题......正如我在最初的回答中已经暗示的那样。一旦被代理(ContextLoaderListener 和一次未被代理的DispatcherServlet 基本上你正在渲染你所有的事务设置,aop 东西无用),你将加载你的 bean 两次。您的ContextLoaderListener 应该扫描所有但是 @Controller 并且您的DispatcherServlet 应该 扫描@Controller。 (见我修改后的答案)。
  • 从您的 &lt;tx:annotation-driven /&gt; 中删除 mode="aspectj"。因为这需要 AspectJ 加载或编译时编织,而这不是您正在使用的。您也可以删除&lt;context:annotation-driven /&gt;,因为&lt;context:component-scan /&gt; 已经暗示了这一点。 @Transactional 注释实际上应该在您的 SimpleProductManager 上,因为那是您的服务层,因此是事务边界。存储库自动参与此事务。

标签: java spring hibernate spring-mvc jpa


【解决方案1】:

从您的堆栈跟踪来看,您正在加载您的 bean 两次,一次是由已正确定义事务的 ContextLoaderListener 加载的。并且一旦在DispatcherServlet 中没有交易。 (您的堆栈跟踪没有显示TransactionInterceptor,这让我相信这一点)。

确保不要两次扫描相同的组件(不要在两个配置文件中重复 &lt;context:component-scan /&gt; 元素。

问题在于您的 app-config.xml

中的以下行
<context:component-scan base-package="org.springtest.mavenspringapp" />

再次会创建所有 bean 的新实例,并且不会对其应用事务。 AOP 仅适用于定义 bean 的同一应用程序上下文中。所以基本上你有 2 个 JPAProductDao 实例(其中无用)与事务以及 ApplicationContext 加载的 ContextLoaderListener 中未应用的实例@另一个(未应用事务)在您的DispatcherServlet 加载的ApplicationContext 中。

修改组件扫描你的 applicationContext.xml 到以下

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

并在您的 app-config.xml 中添加以下内容(注意:不要忘记 use-default-filters 属性!!!!)

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

您还可以删除使用 &lt;context:component-scan /&gt; 已经暗示的 &lt;context:annotation-config /&gt;。为您节省另一行 xml。

【讨论】:

    【解决方案2】:

    您没有收到异常提示的交易:javax.persistence.TransactionRequiredException: no transaction is in progress

    您需要一个总体事务,因为您传递给 saveProduct 方法的对象已被分离(它不再由实体管理器管理)。

    @Transactional 注释你的控制器,你应该很好。请记住,在您获取对象后,它们仍然需要被管理以更新它们。获取对象时还要删除readOnly = true。此外,您实际上不需要存储库上的 @Transactional 注释,因为在所有情况下,我认为您需要一个总体事务,并且不需要从数据源读取的事务。

    【讨论】:

    • 在 \@Controller 之后我添加了一个 \@Transactional 注释。但仍然没有坚持。
    • 您是否也从您的 JPAProductDao 中删除了 @Transactional 注释?
    • 您可能还需要在配置中声明注释驱动的事务管理,如下所示:
    • 注意你的配置之间的 mode="aspectj" 区别
    • 我注意到了不同之处,所以我用您提供的声明替换了我的。我还删除了 \Transactional 但它还没有工作。
    【解决方案3】:

    尝试:

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

    documentation here

    【讨论】:

    • 我添加了你的建议但仍然没有坚持
    • 但是你在flush时遇到了同样的异常吗?
    • 是的,同样的例外
    • 您的@transactional 没有被扫描,所以我不确定您拥有的其他软件包,但我建议删除所有内容并只留下 (如果 org.springtest.mavenspringapp 是你的基础包),我也建议在 EntityManager 上使用 EntityManagerFactory ,因为你用 @PersistenseContext 注释它你不需要setter(你可以使用它与自动连线相同)
    猜你喜欢
    • 2013-09-05
    • 2017-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-30
    • 1970-01-01
    相关资源
    最近更新 更多