SpringMVC中Could not obtain transaction-synchronized Session for current thread的解决方案

首先列出错误信息

org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
    at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:134)
    at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
    以下错误省略

背景

已经配置了声明式事务管理,而且单独测试service方法没错,只有在SpringMVC中才会报错。

声明式事务管理的配置

spring-tx.xml


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    
    <context:property-placeholder location="classpath:jdbc.properties"
        file-encoding="utf-8" ignore-unresolvable="true" />

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan">
            <list>
                
                <value>com.gwc.learn.spring.entityvalue>
            list>
        property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}prop>
                <prop key="hibernate.dialect">${hibernate.dialect}prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}prop>
                <prop key="hibernate.temp.use_jdbc_metadata_defaults">falseprop>
                <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext
                prop>
            props>
        property>
    bean>

    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close" p:driverClass="${jdbc.driverClassName}"
        p:jdbcUrl="${jdbc.url}" 
        p:user="${jdbc.username}" p:password="${jdbc.password}"
        p:testConnectionOnCheckout="${jdbc.c3p0.testConnectionOnCheckout}"
        p:testConnectionOnCheckin="${jdbc.c3p0.testConnectionOnCheckin}"
        p:idleConnectionTestPeriod="${jdbc.c3p0.idleConnectionTestPeriod}"
        p:initialPoolSize="${jdbc.c3p0.initialPoolSize}" p:minPoolSize="${jdbc.c3p0.minPoolSize}"
        p:maxPoolSize="${jdbc.c3p0.maxPoolSize}" p:maxIdleTime="${jdbc.c3p0.maxIdleTime}" />

    
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    bean>

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

    
    
    <aop:config proxy-target-class="true">
        
        <aop:pointcut expression=" execution(* com.gwc.learn.spring.service..*(..))"
            id="serviceMethod" />
        
        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
    aop:config>
    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        
        <tx:attributes>
            <tx:method name="*" />
        tx:attributes>
    tx:advice>

beans>

spring-core.xml


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    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">

    
    <context:component-scan base-package="com.gwc.learn.spring">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    context:component-scan>

    
    <import resource="classpath:spring/spring-tx.xml" />

beans>

对UserService的单独测试

package com.gwc.learn.spring.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.gwc.learn.spring.entity.User;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/spring-core.xml" })
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void testAddUser() {
        User user = new User();
        user.setUsername("ladygaga");
        user.setPassword("123");
        System.out.println(userService.addUser(user));
    }

}

单独测试UserService正常执行
这里写图片描述

接下来看一下在Controller中调用Service的方法

package com.gwc.learn.spring.controller.admin;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({ 
        @ContextConfiguration(name = "parent", locations = "classpath:spring/spring-core.xml"),
        @ContextConfiguration(name = "child", locations = "classpath:spring/spring-mvc.xml") })
public class AddUserControllerTest {
    @Autowired
    private WebApplicationContext wac;
    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void testAddUser() throws Exception {
        mockMvc.perform((post("/admin/addUser.html")
                .param("username", "ladygaga")
                .param("password", "1234")))
                .andExpect(status().isOk()).andDo(print());
    }

}

在调用的时候报错如下

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:980)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:870)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:844)
    at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
    at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:155)
    中间省略
    Caused by: org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
    at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:134)
    at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
    省略

关于网上的各种关于

Request processing failed; nested exception is org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread

的解决方案都不太适合我的问题。

思来想去,最后发现是父子容器的问题,这个通过对Controller的测试代码就可以看出。

根源所在

给出spring-mvc.xml的配置

 <context:component-scan base-package="com.gwc.learn.spring">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    context:component-scan>

再来看看spring-core.xml的配置

  
    <context:component-scan base-package="com.gwc.learn.spring">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    context:component-scan>

这就是问题的关键所在,spring-core中的UserService中的方法是有事务特性的
而spring-mvc中的UserService是没有事务特性的

我们看看spring的文档

As detailed in Section 6.15, “Additional Capabilities of the ApplicationContext”, ApplicationContext instances in Spring can be scoped. In the Web MVC framework, each DispatcherServlet has its own WebApplicationContext, which inherits all the beans already defined in the root WebApplicationContext. These inherited beans can be overridden in the servlet-specific scope, and you can define new scope-specific beans local to a given Servlet instance.

给个图片
这里写图片描述
再给个官方的解释

Note

@EnableTransactionManagement and only looks for 
@Transactional on beans in the same application context they are defined in. This 
means that, if you put annotation driven configuration in a WebApplicationContext for a 
DispatcherServlet, it only checks for @Transactional beans in your controllers, and not 
your services. See Section 21.2, “The DispatcherServlet” for more information.

也就是说DispatcherServlet只会在Controller中找@Transcational注解,不会在Service中。

上面只说了注解开发,通过实验我们知道声明式事务管理照样也使用。

好了,我们不应该让spring-mvc.xml去扫描Service的

解决方案

只要将spring-mvc.xml中的配置进行如下修改即可。

    
    <context:component-scan base-package="com.gwc.learn.spring">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    context:component-scan>

看一下运行Controller的截图
这里写图片描述
这就OK了。

参考

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-introduction

你可能感兴趣的:(Spring/Spring,MVC框架,Hibernate框架)