关于Could not obtain transaction-synchronized Session for current thread 这个异常。

Could not obtain transaction-synchronized Session for current thread 这个异常之前非常让我头大。对于网上的各种说法都试了一下反正都不行。现在终于使这个异常消失了,但是现在(2017-7-8)还搞不清真正的原因。

这是异常的一部分。

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:988)
    at com.zcd.ssh.dao.impl.BaseDao.getSession(BaseDao.java:14)
    at com.zcd.ssh.dao.impl.DepartmentDaoImpl.save(DepartmentDaoImpl.java:16)
    at com.zcd.ssh.test.SSHTest.testCRUD(SSHTest.java:42)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

 

现在先把我猜测的原因写下来,之后如果有新发现再来这里更新这篇文章。

一、当我整合 Spring 和 Hibernate 的时候先配置好 IOC 容器的 applicationContext.xml 文件中的部分内容,创建实体类 Employee 类和Department类, Dao层的EmployeeDao类和DepartmentDao类等等,进行JUnit测试。

 

==========================================================================

applicationContext.xml 文件的部分内容。连接数据库需要的 db.properties 文件就不贴出来。

<!-- 扫描包 -->
    <context:component-scan base-package="com.zcd.ssh"></context:component-scan>
    
    <!-- 导入资源文件: db.properties文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置数据源,使用c3p0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${user}"></property>
        <property name="password" value="${password}"></property>
        <property name="driverClass" value="${driverClass}"></property>
        <property name="jdbcUrl" value="${jdbcUrl}"></property>
        <property name="maxPoolSize" value="${maxPoolSize}"></property>
        <property name="minPoolSize" value="${minPoolSize}"></property>
        <property name="initialPoolSize" value="${initialPoolSize}"></property>
        <property name="acquireIncrement" value="${acquireIncrement}"></property>
    </bean>
    
    <!-- 配置SessionFactory 使用LocalSessionFactoryBean-->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
        <property name="mappingLocations" value="classpath:com/zcd/ssh/beans/*.hbm.xml"></property>
    </bean>

 

==========================================================================

BaseDao类

/**
 * 其他Dao类通用的方法。
 * @author ZCD
 */
@Repository
public class BaseDao
{
    @Autowired
    private SessionFactory sessionFactory;
    
    /**
     * 获取当前线程的Session。
     * @return 返回当前线程的Session
     */
    public Session getSession()
    {
        return sessionFactory.getCurrentSession();
    }
}

==========================================================================

DepartmentDaoImpl类。

@Repository
public class DepartmentDaoImpl extends BaseDao implements DepartmentDao
{
    @Override
    public boolean save(Department department)
    {
        getSession().save(department);
        
        return true;//暂时返回true。
    }

    @Override
    public boolean delete(Integer id)
    {
        String hql = "DELETE FROM Department d WHERE d.id = ?";
        
        getSession().createQuery(hql).setInteger(0, id);
        
        return true; //暂时返回true。
    }

    @Override
    public Department getDepartment(Integer id)
    {
        String hql = "FROM Department d WHERE d.id = ?";
        
        Department department = (Department)getSession().createQuery(hql).setInteger(0, id);
        
        return department;
    }

    @Override
    public ArrayList<Department> getDepartments()
    {
        String hql = "FROM Department d";
        
        ArrayList<Department> departments = (ArrayList<Department>)getSession().createQuery(hql).list();
        
        return departments;
    }
}

 

==========================================================================

@Repository
public class EmployeeDaoImpl extends BaseDao implements EmployeeDao
{

    @Override
    public boolean save(Employee employee)
    {
        getSession().save(employee);
        
        return true; //暂时返回true。
    }

    @Override
    public boolean delete(Integer id)
    {
        String hql = "DELETE FROM Employee e WHERE e.id = id";
        
        getSession().createQuery(hql).setInteger(0, id).executeUpdate();
        
        return true; //暂时返回true。
    }

    @Override
    public Employee getEmployee(Integer id)
    {
        String hql = "FROM Employee e WHERE e.id = ?";
        
        Employee employee = (Employee) getSession().createQuery(hql).setInteger(0, id).uniqueResult();
        
        return employee;
    }

    @Override
    public ArrayList<Employee> getEmployees()
    {
        String hql = "FROM Employee e";
        
        ArrayList<Employee> employees = (ArrayList<Employee>) getSession().createQuery(hql).list();
        
        return employees;
    }
}

 

==========================================================================

进行单元测试时出现了异常。

public class SSHTest
{
    private ApplicationContext ac;
    private DataSource dataSource;private DepartmentDao departmentDao;
    
    
    {
        ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        dataSource = ac.getBean(DataSource.class);
        
        departmentDao = ac.getBean(DepartmentDao.class);
    }
    
    /*
     * 测试是否能正常连接数据库,并且自动生成数据表。
     */
    @Test
    public void testConnection() throws SQLException
    {
        System.out.println(dataSource.getConnection().getClass().getName());
    }
    
    /*
     * 测试DepartmentDao类的增删改查功能。
* 当我测试一下保存一个部门对象时,就抛出了上述异常。
*/ @Test public void testCRUD() { departmentDao.save(new Department(102, "财务部")); } }

 

==========================================================================

二、当我完善了我的 applicationContext.xml文件,并且创建Service层等等后,在进行上述单元测试时就能够正常执行操作。

在applicationContext.xml中添加了一下内容。

<!-- 使用Spring的声明式事务 -->
    
    <!-- 1、配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"></property>
    </bean>
    
    <!-- 2、配置事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 3、配置事务切点,并且将事务切点和事务通知关联起来。 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.zcd.ssh.service.*.*(..))" id="txPointcut"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>

 

==========================================================================

Service层的 OperateDepartmentService 类其实和Dao层的 DepartmentDao 类基本没区别。为了使这个项目的结构完整才添加了。

@Service
public class OperateDepartmentService
{
    @Autowired
    private DepartmentDao departmentDao;
    
    /**
     * 保存部门对象。保存新建的部门或修改的部门对象。
     * @param department 被保存的部门对象。
     * @return 保存成功返回true,否则返回false。
     */
    public boolean save(Department department)
    {
        boolean success = departmentDao.save(department);
        
        return success;
    }
    
    /**
     * 根据部门id删除部门对象。
     * @param id 部门id.
     * @return 删除成功返回true,否则返回false。
     */
    public boolean delete(Integer id)
    {
        boolean success = departmentDao.delete(id);
        
        return success;
    }
    
    /**
     * 根据部门id查询部门对象。
     * @param id 部门id。
     * @return 返回一个部门对象。
     */
    public Department getDepartment(Integer id)
    {
        Department department = departmentDao.getDepartment(id);
        
        return department;
    }
    
    /**
     * 查询所有部门对象。
     * @return 返回所有部门对象。
     */
    public ArrayList<Department> getDepartments()
    {
        ArrayList<Department> departments = departmentDao.getDepartments();
        
        return departments;
    }
}

 

==========================================================================

再次进行单元测试就能正常进行操作了。

@Test
    public void testCRUD()
    {
        operateDepartmentService.save(new Department(101, "销售部"));
    }

 ==========================================================================

                                    新发现

 ==========================================================================

 三、在 Spring 整合 Hibernate 后进行单元测试什么都没有出现问题。但是加入SpringMVC后感觉自己写的代码都没有错误。

 ==========================================================================

下面是SpringMVC的配置文件。

    <!-- 扫描包 -->
    <context:component-scan base-package="com.zcd.ssh" use-default-filters="true">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>
    
    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    
    <mvc:default-servlet-handler/>
    
    <mvc:annotation-driven></mvc:annotation-driven>

 

 ==========================================================================

web.xml 文件。

<!-- 配置contextConfigLocation -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!-- Bootstraps the root web application context before servlet initialization -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- 配置DispatcherServlet -->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <!-- 拦截所有请求 -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <!-- 拦截所有请求 -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

 ==========================================================================

jsp页面的请求。

<a href="employees">所有员工</a>

 

 ==========================================================================

@Controller
public class EmployeeController
{
    @Autowired
    private OperateEmployeeService employeeService;
    
    @RequestMapping(value="/employees", method=RequestMethod.GET)
    public String list(Map<String, Object> map)
    {
        ArrayList<Employee> employees = employeeService.getEmployees();
        map.put("employees", employees);
        
        return "employees";
    }
}

我以上的代码感觉都没有问题。但却总是抛出异常。真正的原因就是加入SpringMVC后,我又配置了一个SpringMVC的IOC容器文件:springmvc.xml 。此时有两个IOC容器的配置文件。一个是SpringMVC的,一个是Spring的。这两个文件都对bean进行了自动扫描。

springmvc.xml 文件扫描的代码如下。这里是只扫描@Controller 注解的类。

    <!-- 扫描包 -->
    <context:component-scan base-package="com.zcd.ssh">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

applicationContext.xml 文件的扫描的代码如下。这是除了@Controller 注解的类都扫描。看似很完美。刚好所有的bean都扫描完了,每个bean都只会创建一个实例。

    <!-- 扫描包 -->
    <context:component-scan base-package="com.zcd.ssh">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

但是真的只会创建一个实例吗?在每个bean的无参构造器都打印一句话,然后运行程序。发现,除了 @Controller 注解的类,其他的类都创建了两个实例。也就是说我们在Spring IOC容器里声明的SessionFactory这个bean也创建了两个实例。问题可能就是因为创建了两个SessionFactory的实例,所以不能获取当前线程的Session。

此时只要在springmvc.xml扫描<context:component-scan>节点加上use-default-filters="false" 这个属性。就可以了。因为不加这个它还是会使用默认的过滤器。还是会扫描到其他的Spring IOC容器中的bean。具体修改如下

    <!-- 扫描包 --><!-- 加上 use=default-filters="false" 这个属性就可以了-->
    <context:component-scan base-package="com.zcd.ssh" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

====================================================================

2017-09-25

 

 

 

 

四、总结

       观察了一下,会出现上述异常原因:

  一、因为没有通过Service层来访问数据库,更准确的说没有使用声明式事务。因为Hibernate 使用Spring的声明式事务,事务的切点是在Service层里的所有类的所有方法。所以如果直接通过Dao层操作数据库就相当于没有使用事务,如果事务切点配置错误也有可能出现异常。还有,如果一个业务层(Service层)中的某一个类已经使用@Service注解,但是事务切点没有作用在这个类的方法上,那么也就相当于没有使用事务,也会出现上述异常。

以上猜测不知道正不正确,反正异常消失了。^o^

 

posted @ 2017-07-08 22:54  钓鱼翁  阅读(6709)  评论(0编辑  收藏  举报