Top / Hibernate / 悲観的ロックを実装する

悲観的ロックとは

※私はデータベースの専門家でもないですし、OracleやHibernateのエキスパートでもないのでココに書いてある内容は正しいかどうか保証できません。あしからずご了承ください

Hibernate/楽観的ロックを実装する で楽観的ロックの実装については試してみました。次は悲観的ロックを試してみようと思います。悲観的ロックとは、複数のトランザクションが同じrowを更新してしまわないように、トランザクション中はそのrowを占有ロックしてしまおうという方法です。あるトランザクションがあるrowに対して悲観的ロックをかけると、他のトランザクションはそのrowを参照することができなくなります。ロックを取得したトランザクションが終了するまで待ちになったり、参照しようとした時点で例外になったりします。ようするにOracleなどでいうところのselect 〜 for updateの事ですね。

ところでこのOracleのselect for updateですが、私は勘違いしていました。つまり

トラン1がselect for update で占有ロックする
トラン2がselectしようとしたら、待ちor例外

だと思っていたんですが、違いました。正しくは

トラン1がselect for update で占有ロックする
トラン2がselect for update しようとしたら、待ちor例外

なんですね。つまりあるトランザクションが占有ロックをしても、単純なselectは実行できちゃいました。これって正しい動き???知ってるヒト、教えてください。環境はOracle 10g XEです。

Hibernateでの悲観的ロックの実装方法

Hibernateで悲観的ロックを取得するには、update前のselect時に、ロックするという指定をしておけばOKです。具体的には

UserAttr user = (UserAttr) session.load(UserAttr.class, key,LockMode.UPGRADE);

とloadやget時に LockMode?.UPGRADE という占有ロックするという指定をするだけです。

やってみる

テーブル構成などは Hibernate/Springを使ってトランザクション処理を記述する の構成を用いてサンプルを実行してみます。やっている内容は

ということですね。

ソース

詳細はソースを見て欲しいですが、抜粋するとこんな感じです。

new Thread(new Runnable() {
  public void run() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    System.out.println("トラ1開始");
    // select for updateする
    UserAttr user = (UserAttr) session.load(UserAttr.class, key, LockMode.UPGRADE);
    System.out.println("トラ1の取得結果" + user);
    // このトランザクション内でカラムを変更し
    user.setName("Thread1:" + System.currentTimeMillis());
    waitt(3000);

    // 上で3000msわざと待ちにしたこのスキにトランザクション2のスレッドが動き出す
    // この間にアクセスしたトランはロック解除待ちになるはず
    tx.commit();
    session.close();
    System.out.println("トラ1終了");
    // 最後に表示する。
    print();
    System.out.println("トラ1終了");

  }

}).start();

new Thread(new Runnable() {
  public void run() {
    waitt(1000);
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    System.out.println("トラ2開始");
    // 普通にselectしようとする。待ちにはいるはず
    UserAttr user = (UserAttr) session.load(UserAttr.class, key,
        LockMode.UPGRADE);// ココもロックをつけないと、検索できちゃうんだね。
    System.out.println("トラ2の取得結果" + user);
    tx.commit();
    session.close();
    System.out.println("トラ2終了");
  }
}).start();

実行結果

実行結果は以下の通り

org.hibernate.impl.SessionFactoryImpl - building session factory
ユーザ作成のトラン開始
org.hibernate.SQL - insert into MKINO.USER_ATTR (NAME, USERID) values (?, ?)
ユーザ作成のトラン終了
Keyは abc40619
トラ1開始
[Thread-0] DEBUG org.hibernate.SQL - select userattr0_.USERID as USERID0_0_, userattr0_.NAME as NAME0_0_ 
  from MKINO.USER_ATTR userattr0_ where userattr0_.USERID=? for update
トラ1の取得結果nu.mine.kino.entity.UserAttr@10e35d5[id=abc40619,name=サンプルユーザ]
トラ2開始
[Thread-1] DEBUG org.hibernate.SQL - select userattr0_.USERID as USERID0_0_, userattr0_.NAME as NAME0_0_ 
  from MKINO.USER_ATTR userattr0_ where userattr0_.USERID=? for update
[Thread-0] DEBUG org.hibernate.SQL - update MKINO.USER_ATTR set NAME=? where USERID=?
トラ2の取得結果nu.mine.kino.entity.UserAttr@6a3960[id=abc40619,name=Thread1:1178297648171]
トラ1終了
トラ2終了

このように

となりました。確かにトランザクション2は待ち状態になったようです。

待ちではなく、あきらめる

待ち状態にするのではなく、すぐにあきらめるようにするにはロックの指定を

前:session.load(UserAttr.class, key,LockMode.UPGRADE);
後:session.load(UserAttr.class, key,LockMode.UPGRADE_NOWAIT);

に変更します。実行すると

Exception in thread "Thread-1" org.hibernate.exception.LockAcquisitionException:
could not load an entity: [nu.mine.kino.entity.UserAttr#abc384]

という例外がスローされました。


この記事は

選択肢 投票
おもしろかった 25  
そうでもない 2  

Top / Hibernate / 悲観的ロックを実装する

現在のアクセス:45719


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS