对于才接触hibernate的初学者来说可能会遇到的一个问题就是hibernate的session的管理问题,简单举一个列子,假设一个电商网站,我要读取产品信息,用户发起请求后我们后台去数据库查询产品信息,代码上我们可能是这样的操作
Session session = HibernateUtil.openSession();
session.beginTransaction();
Product product = session.get(Product.class, id);
session.getTransaction().commit();
session.close();
然后我们把product传到jsp页面,jsp页面做数据渲染,可是jsp页面需要的数据不仅仅有产品名称,价格信息,可能还需要产品的评价等,
我简单罗列两个类Product(产品)和Order_record(订单记录)两个类
package xgny.hb.entity.product; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; import xgny.hb.entity.user.Order_record; @Entity @Table(name="product") public class Product implements Serializable{ /** * 序列化 */ private static final long serialVersionUID = 1L; /* * 标识属性 */ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; /* * 名称 */ private String name; /* * 价格 */ private double price; /* * 描述 */ private String describle; /* * 库存 */ private double stock; /* * 分类 */ @ManyToOne(targetEntity=Category.class, fetch=FetchType.LAZY) @JoinColumn(name="category_id" , referencedColumnName="id", nullable=false) private Category category; /* * 订单记录 */ @OneToMany(targetEntity=Order_record.class, mappedBy="product", fetch=FetchType.LAZY) private List<Order_record> order_records = new ArrayList<>(); /* * 图片 */ private String image; /* * 销售量 */ private double sales_volume; /* * 是否上架 默认不上 */ private int on_shelf = 0; /* * 参与的活动 */ @ManyToOne(targetEntity=Sale_promotion.class, fetch=FetchType.LAZY) @JoinColumn(name="sale_promotion_id" , referencedColumnName="id", nullable=true) private Sale_promotion sale_promotion;
}//省略get与set...
package xgny.hb.entity.user; import java.io.Serializable; import java.util.Date; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; import javax.persistence.Table; import xgny.hb.entity.product.Product; @Entity @Table(name="order_record") public class Order_record implements Serializable { /** * 序列化 */ private static final long serialVersionUID = 1L; /* * 标识属性 */ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; /* * 物流编号 */ private String logistics_id; /* * 用户 */ @ManyToOne(targetEntity=User.class, fetch=FetchType.LAZY) @JoinColumn(name="account" , referencedColumnName="account", nullable=false) User user; /* * 时间 */ private Date date; /* * 产品 */ @ManyToOne(targetEntity=Product.class, fetch=FetchType.LAZY) @JoinColumn(name="product_id" , referencedColumnName="id", nullable=false) private Product product; /* * 状态 */ private int state; /* * 数量 */ private int number; /* * 价格 */ private double price; /* * 买家留言 */ private String message; /* * 评价 */ @OneToOne(targetEntity=Evaluate.class, mappedBy="order_record", fetch=FetchType.LAZY) private Evaluate evaluate; /* * 收获地址 */ @ManyToOne(targetEntity=Receiving_addr.class, fetch=FetchType.LAZY) @JoinColumn(name="addr_id" , referencedColumnName="id", nullable=false) private Receiving_addr addr;
//省略get与set...
}
当我们在jsp页面中使用el表达式调用${product.order_record.date}的时候系统会报"No Session"因为hibernate的session已经关闭了,这是因为我们在关联时采用的延迟加载,延迟加载数据就是当我们不使用这个数据的时候不会去数据库读取这个信息,当我们使用的时候才会去读取数据,那么问题就来了,我们开始在数据库查询数据的时候只查询了product,并没有查询product的order_record,我们在jsp数据渲染的时候去查询了order_record但是我们hibernate的session已经关闭了,一种方法是取消延迟加载,但是这样会产生系统性能的下降,那自然就会想到要是session是在jsp渲染数据之后才关闭就好了。所以解决方式就出来了利用ThreadLocal线程变量来解决这个问题,线程变量有一个特点就是每一个线程都会独立拥有一个改变量,拿到这里来说的话就是每一个变量都会独立拥有一个session,整个线程都可以使用这个session,而不会造成线程死锁,我贴上代码来详细说明。
package xgny.hb.util; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; public class HibernateUtil { private static Log log = LogFactory.getLog(HibernateUtil.class); private static SessionFactory sessionFactory = null; private static ThreadLocal<Session> local = new ThreadLocal<Session>(); //大家注意这一行,可能很多人就会问这是一个静态的变量啊,怎么实现 每一个线程都有一个session的呢?而不会线程死锁,这个就是线程变量的巧妙的地方,其实线程变量在每一个获得session的时候都是获得的一个session的复制品, 这样就相当于每一个线程都有一个session变量 static{ final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build(); try { sessionFactory = new MetadataSources( registry ).buildMetadata().buildSessionFactory(); log.info("Hiberbate获取SessionFactory成功!"); } catch (Exception e) { log.fatal("Hiberbate获取SessionFactory失败,系统运行终止!"); StandardServiceRegistryBuilder.destroy( registry ); e.printStackTrace(); } } /* * 获取 SessionFactory 保留方法 */ public static SessionFactory getSessionFactory() { return sessionFactory; } /* * 获取session */ public static Session openSession(){ Session session = local.get(); if(session == null||!session.isOpen()){ session = sessionFactory.openSession(); local.set(session); } return session; } /* * 关闭session */ public static void closeSession(){ Session session = local.get(); local.set(null); if(session!=null){ session.close(); } } }
然后我们利用filter来实现session的自动开启和关闭,代码如下
package xgny.hb.util; 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 javax.servlet.annotation.WebFilter; /** * Servlet Filter implementation class HibernateFilter */ @WebFilter("*.xgny") public class HibernateFilter implements Filter { /** * Default constructor. */ public HibernateFilter() { // TODO Auto-generated constructor stub } /** * @see Filter#destroy() */ public void destroy() { // TODO Auto-generated method stub } /** * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // TODO Auto-generated method stub // place your code here // pass the request along the filter chain HibernateUtil.openSession(); //前处理打开session chain.doFilter(request, response); HibernateUtil.closeSession(); //后处理关闭session } /** * @see Filter#init(FilterConfig) */ public void init(FilterConfig fConfig) throws ServletException { // TODO Auto-generated method stub } }
简单讲就是在filter前处理中打开session在filter后处理中关闭session,这个地方需要我们理解J2EE的请求相应流程一般步骤:用户发起请求,请求经过过滤器(前处理),响应的servlet或者jsp响应,再经过filter过滤器(后处理),最后用户