ThreadLocal でHibernate Session を効率的に管理する

2008/08/05 23:57Update
TAGS: Hibernate | Session | スレッドセーフ | ThreadLocal

Hibernate Session を使用する原則、効率よく利用するための利用方法、スレッドセーフの必要性などについて学びます、最後にThreadLocal Session及びOpen Session In Viewを用いてその 実装例をあげます。

Hibernate Sessionはスレッドセーフではありません、即ち、同一Sessionが複数のスレッドに共有されてしまうと、競合状態を引き起こします。そのため、Sessionを有効に管理する必要があります。

Hibernate Sessionを使用する原則


◇ Sessionがスレッド内に限って使用すべきです。上も述べたように、Sessionインスタンスを複数のスレッドに共有すると、問題を起こします。Sessionインスタンスが確実に一つのスレッドにしか使われていないことを確認しましょう。
◇ データベースの操作を終わると、Sessionを閉じるべきです。開いたSessionをそのまま放置すると、そのコレクションも解放されない可能性がありますので、try ~finally ブロックの中で、Session.close()メソッドで開いたSessionを確実に閉じてください。
◇ Sessionを効率よく使いましょう。同じスレッド内でSessionを何度も開いたり、閉じたりすることは、Sessionが有効に利用されていることではありません。基本的に、スレッド内のSessionインスタンスを共有しても、全く問題がありません、できるだけスレッド内でSessionインスタンスを共有しましょう。

Hibernate Sessionを効率よく利用するには


■ThreadLocal Session
Sessionインスタンスをスレッド内で有効に共有するために、ThreadLocalというクラスを利用することはその実装方法の一つです。
ここでは、TheadLocalクラスに対して詳しい説明を省きますが、これだけを覚えておきましょう:ThreadLocalはスレッド内で一意になるオブジェクトの値を格納する、そして持ちまわすことができます。

TheadLocalでスレッド内で一意なHibernate Sessionを持ちまわすための 実装例:
※次のソースは実行可能なものですが、その正確性をいかなる保障致しませんので、ご了承ください。
※ご利用は自由(フリー)です。

HibernateUtils.java
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

/**
 * Sessionを安全に使うためのHibernate Sessionユーティリティクラス
 * @author: http://www.syboos.jp
 */
public class HibernateUtils {
    private static SessionFactory sessionThreadLocalFactory = null;
    
    /**
     * <code>sessionThreadLocal</code>
     */
    private static final ThreadLocal sessionThreadLocal = new ThreadLocal();
    
    private HibernateUtils() {
        
    }

    /**
     * 初期化処理
     * (アプリケーションスコープで一回しか呼ばない、明示的に呼ばれたことがなかった場合、currentSession()から自動的に呼ばれます)
     * @throws HibernateException
     */
    public static void initialize()  throws HibernateException {
        _init(null);
    }
    
    /**
     * initialize the Hibernate environment
     * @throws HibernateException
     */
    public static void initialize(String resource)  throws HibernateException {
        _init(resource);
    }
    
    //リソースからSessionFactoryを取得する(アプリケーションスコープで一回しか呼ばない)
    private static void _init(String resource) throws HibernateException {
        try {
            // Create the SessionFactory                
            if (resource != null && !resource.equals("")) {
                sessionThreadLocalFactory = new Configuration().configure(resource).buildSessionFactory();
            } else {
                sessionThreadLocalFactory = new Configuration().configure().buildSessionFactory();
            }
            
        } catch (Throwable ex) {
            throw new HibernateException("Can't build hibernation SessionFactory", ex);
        } // end of the try - catch block
    }
    
    /**
     * スレッド内の一意Sessionインスタンスを取得する
     * @return スレッド内で共有されるSessionのインスタンス
     * @throws HibernateException
     */
    public static Session currentSession() throws HibernateException {
        
        if (sessionThreadLocalFactory == null)
            initialize();
        
        //ThreadLocal変数からSessionを取得する
        Session s = (Session) sessionThreadLocal.get();
        
        // Open a new Session, if this Thread has none yet
        if (s == null) {
            s = sessionThreadLocalFactory.openSession();
            sessionThreadLocal.set(s);
        }
        return s;
    }

    /**
     * Sessionを閉じる
     * @throws HibernateException
     */
    public static void closeSession() throws HibernateException {
        if (sessionThreadLocalFactory == null || sessionThreadLocal == null)
            return;
        
        Session s = (Session) sessionThreadLocal.get();
        sessionThreadLocal.set(null);
        if (s != null)
            s.close();
    } // end of the method
    
} // end of the class 


■Open Session In View
WEBシステムでは、スレッドのライフサイクルは一つのリクエストと同じです。そのため、Sessionは、リクエストがサーバに届くから、レスポンスを送信する直前まで、各ビジネスロジックに共有されれば、効率であると思われます。

Open Session In ViewはServlet Filterを利用して、doFilterメソッドの最後にSessionを閉じたり、トランザクションをコミットしたりします。
上のHibernateUtilsとあわせて使用すると、各ビジネスロジックの中で、
HibernateUtils.currentSession()よりスレッド内のSessionインスタンスを取得して、Sessionを管理する意識せずにデータベースを操作することができます。また、明示的にSessionを閉じる必要もありません。

OpenSessionInViewFilter.java
public class OpenSessionInViewFilter implements Filter {
...
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
            
            try {
                //初めからSessionをオープンしておく
                //HibernateUtils.openSession();
                chain.doFilter(request, response);
                
            } finally {
                try {
                    //ビューの描画が終了する際、自動的にHibernateセッションを閉じる
                    HibernateUtils.closeSession();
                } catch (Exception ex) {
                    log.error(ex.getCause());
                    throw new ServletException(ex);
                }
            }
    }
...
}


Open Session In Viewの詳細は次の記事をご参照ください。

Hibernate究明 - Hibernate のLAZYロード及びOpenSessionInView 

有关作者
Syboos.jp編集長AJavaやオープンソース情報の執筆、Webサイトの開発や運営全般の業務に携わる。

Sponsored Link


Comments