在WEB应用程序中,视图(JSP或Servlet)可能会通过导航从某个托管对象中拖出它的关联对象或集合数据,这些关联对象或集合数据如果是被延迟加载的(Hibernate本身就有几种默认延迟加载的情况,欲了解更多请看前面的文章),Hibernate就会抛出LazyInitializationException.因为没有持久化上下文存在时,是无法加载违背初始化的代理和集合的。
针对这个问题,Hibernate作者提出了OpenSessionInView模式作为解决方案,这个模式的主要思想是:
在用户的每一次请求过程始终保持一个持久化上下文打开着
OpenSessionInView模式的具体实现有以下三个步骤:
第一步:把Session绑定到当前线程上
要保证在一次请求中只有一个持久化上细纹,首先要把Session实例绑定到当前线程上,这需要在Hibernate的全局配置文件hibernate.cfg.xml中这样配置(其实就是前面关于Session文章的sf.getCurrentSession的一种配法)
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!--把session附加到当前线程上-->
<property name="hibernate.current_session_context_class">thread</property>
</session-factory>
</hibernate-configuration>
然后,在程序代码中获取Session实例时,使用SessionFactory的getCurrentSession()方法。这样,可以保证每一次请求的处理线程上只有一个持久化上下文实例的存在
第二步:用Filter过滤器在请求到达之前打开Session,在相应返回前关闭Session,代码如下
package com.javacrazyer.common;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
/*OSIV模式的Filter*/
public class OpenSessionInViewFilter implements Filter {
//这个之所以放在这里当成员变量因为它是线程安全的,严格的说,这个实例只能创建一次
private SessionFactory sf;
public void destroy() {
}
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {
Session sesion=null;
try {
//请求到达时,打开Session并启动事务
sesion=sf.getCurrentSession();
sesion.getTransaction().begin();
//执行请求处理链
arg2.doFilter(arg0, arg1);
//返回相应时,提交事务,关闭session
sesion.getTransaction().commit();
} catch (Exception e) {
//执行过程中,如果有异常,则必须回滚事务
if (sesion.getTransaction().isActive()) {
sesion.getTransaction().rollback();
}
}
}
public void init(FilterConfig arg0) throws ServletException {
sf=HibernateUtil.getSessionFactory();
}
}
这段代码在请求处理完毕之后,也就是提交事务之后,并没有书写关闭session的代码,这是因为当前使用的是SessionFactory的getCurrentSession()来获取实例的,在当前线程上的事务被提交或回滚时,当前Session会自动清除和关闭的。所以无需手工关闭
HibernateUtil如下
package com.javacrazyer.common;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
/**
* Hibernate工具类
*
*/
public class HibernateUtil {
private static final SessionFactory factory;
private HibernateUtil(){}
static{
//加载Hibernate全局配置文件,根据配置信息创建SessionFactory工厂实例
factory = new AnnotationConfiguration().configure().buildSessionFactory();
}
public static SessionFactory getSessionFactory(){
return factory;
}
/**
* 本方法用来获取当前线程上绑定的Session实例
* 注意hibernate全局配置文件中,如果使用是JDBC事务,添加如下配置:<br/>
* <property name="hibernate.current_session_context_class">thread</property>
* @return
*/
public static Session getSession(){
return factory.getCurrentSession();
}
}
第三步,在WEB应用的web.xml中配置Filter过滤器
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!--OSIV的过滤器 -->
<filter>
<filter-name>osiv</filter-name>
<filter-class>com.javacrazyer.common.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>osiv</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
为了简单起见,本程序应用到了所有URL,在实际开发中,建议把它应用到执行期间需要操作数据库的URL上