关于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^