在spring MVC和hibernate項目中如何實現hibernate的session在請求發起時開啓數據渲染後自動關閉

對於才接觸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過濾器(後處理),最後用戶









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章