在利用Hibernate開發DAO模塊時,我們和Session打的交道最多,所以如何合理的管理Session,避免Session的頻繁創建和銷毀,對於提高系統的性能來說是非常重要的,以往是通過eclipse的插件來自動完成這些代碼的,當然效果是不錯的,但是總是覺得不爽(沒有讀懂那些冗長的代碼),所以現在打算自己實現Session管理的代碼。
我們知道Session是由SessionFactory負責創建的,而SessionFactory的實現是線程安全的,多個並發的線程可以同時訪問一個SessionFactory並從中獲取Session實例,那麼Session是否是線程安全的呢?很遺憾,答案是否定的。Session中包含了數據庫操作相關的狀態信息,那麼說如果多個線程同時使用一個Session實例進行CRUD,就很有可能導致數據存取的混亂,你能夠想像那些你根本不能預測執行順序的線程對你的一條記錄進行操作的情形嗎?
在Session的眾多管理方案中,我們今天來認識一種名ThreadLocal模式的解決方案。
早在Java1.2推出之時,Java平台中就引入了一個新的支持:java.lang.ThreadLocal,給我們在編寫多線程程序時提供了一種新的選擇。ThreadLocal是什麼呢?其實ThreadLocal並非是一個線程的本地實現版本,它並不是一個Thread,而是thread local variable(線程局部變量)。也許把它命名為ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用非常簡單,就是為每一個使用某變量的線程都提供一個該變量值的副本,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。從線程的角度看,就好像每一個線程都完全擁有一個該變量。
ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於存儲每一個線程的變量的副本。比如下面的示例實現(為了簡單,沒有考慮集合的泛型):
public class ThreadLocal {
private Map values = Collections.synchronizedMap(new HashMap());
public Object get() {
Thread currentThread = Thread.currentThread();
Object result = values.get(currentThread);
if(result == null&&!values.containsKey(currentThread)) {
result = initialValue();
values.put(currentThread, result);
}
return result;
}
public void set(Object newValue) {
values.put(Thread.currentThread(), newValue);
}
public Object initialValue() {
return null;
}
}
那麽具體如何利用ThreadLocal來管理Session呢?Hibernate官方文檔手冊的示例之中,提供了一個通過ThreadLocal維護Session的好榜樣:
public class HibernateUtil {
public static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static final ThreadLocal<Session>session=new ThreadLocal<Session>();
public static Session currentSession() throws HibernateException {
Session s = session.get();
if(s == null) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException {
Session s = session.get();
if(s != null) {
s.close();
}
session.set(null);
}
}
只要借助上面的工具類獲取Session實例,我們就可以實現線程范圍內的Session共享,從而避免了線程中頻繁的創建和銷毀Session實例。當然,不要忘記在用完後關閉Session。寫到這裡,想再多說一些,也許大多數時候我們的DAO並不會涉及到多線程的情形,比如我們不會將DAO的代碼寫在Servlet之中,那樣不是良好的設計,我自己通常會在service層的代碼裡訪問DAO的方法。但是我還是建議采用以上的工具類來管理Session,畢竟我們不能僅僅考慮今天為自己做什麼,還應該考慮明天為自己做什麼!