|
Hibernateは業務用アプリとかで出てくる、楽観的ロック機構を標準でサポートしていますので、その機能を試してみました。楽観的ロックとは、複数のトランザクションが同じrowを更新しようとしたときに、それを検知する仕組みです。検知とは、先勝ちしたり、後勝ちしたりといろいろあるみたいですが、後にコミットかけた方で例外が発生する、が一般的なのではないでしょうか。 実装方法は対象のテーブルにバージョンカラムを設けて、更新されたらインクリメントされるようにしておきます。で、それぞれのトランザクションが更新するときに、検索時のバージョンの値と、更新時のテーブルのバージョンの値を見比べて、同じだったら誰も更新されていない、違ったら誰かが更新している、ということを検知するわけです。 対象テーブルの情報 †さて実験ですが、複数のトランザクションである同じrowを検索してきて、それをそれぞれのトランザクションでupdateしてみようと思います。 対象のテーブルMESSAGESは以下の通り: mysql> describe test.MESSAGES; +-----------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+------------------+------+-----+---------+----------------+ | MESSAGE_ID | int(10) unsigned | NO | PRI | NULL | auto_increment | | NEXT_MESSAGE_ID | int(10) unsigned | YES | MUL | NULL | | | MESSAGE_TEXT | varchar(255) | NO | | NULL | | | VERSION | int(10) unsigned | NO | | 0 | | +-----------------+------------------+------+-----+---------+----------------+ このテーブルを作成するために実行するDDLは以下の通り: CREATE TABLE `MESSAGES` ( `MESSAGE_ID` int(10) unsigned NOT NULL auto_increment, `NEXT_MESSAGE_ID` int(10) unsigned default NULL, `MESSAGE_TEXT` varchar(255) character set ujis NOT NULL default '', `VERSION` int(10) unsigned NOT NULL default '0', PRIMARY KEY (`MESSAGE_ID`), KEY `MESSAGES_FKIndex1` (`NEXT_MESSAGE_ID`), CONSTRAINT `MESSAGES_ibfk_1` FOREIGN KEY (`NEXT_MESSAGE_ID`) \ REFERENCES `MESSAGES` (`MESSAGE_ID`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=latin1 ; Messages.hbm.xml †Hibernateの設定ファイルは以下の通り。VERSIONのカラムの指定が、 <version name="version" type="int"> <column name="VERSION" not-null="true"> </column> </version> となっています。 ちなみにこのファイルはHibernate Toolsによって作成されました。 <?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2006/06/19 0:06:47 by Hibernate Tools 3.1.0.beta5 -->
<hibernate-mapping>
<class name="nu.mine.kino.models.Messages" table="MESSAGES" catalog="test">
<comment></comment>
<id name="messageId" type="int">
<column name="MESSAGE_ID" />
<generator class="increment" />
</id>
<version name="version" type="int">
<column name="VERSION" not-null="true">
<comment></comment>
</column>
</version>
<many-to-one name="messages" class="nu.mine.kino.models.Messages" fetch="select">
<column name="NEXT_MESSAGE_ID">
<comment></comment>
</column>
</many-to-one>
<property name="messageText" type="string">
<column name="MESSAGE_TEXT" not-null="true">
<comment></comment>
</column>
</property>
<set name="messageses" inverse="true">
<key>
<column name="NEXT_MESSAGE_ID">
<comment></comment>
</column>
</key>
<one-to-many class="nu.mine.kino.models.Messages" />
</set>
</class>
</hibernate-mapping>
やってみる †以下のように、複数のトランザクションを別スレッドで稼働させてUpdateがどうなるかを見てみました。 new Thread(new Runnable() {
public void run() {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
System.out.println("トラ1開始");
Messages message = (Messages) session.load(Messages.class,new Integer(1));
System.out.println(message.getMessageText());
message.setMessageText("Thread1:" + System.currentTimeMillis());
waitt(3000);
tx.commit();
session.close();
System.out.println("トラ1終了");
}
}).start();
new Thread(new Runnable() {
public void run() {
waitt(500);
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
System.out.println("トラ2開始");
Messages message = (Messages) session.load(Messages.class,new Integer(1));
System.out.println(message.getMessageText());
waitt(1000);
message.setMessageText("Thread2:" + System.currentTimeMillis());
tx.commit();
session.close();
System.out.println("トラ2終了");
}
}).start();
このように トラ1開始 select -> トラ2開始 select -> トラ2update & commit -> トラ1update & commit としたときに、トラ1のコミットがどうなるかを見てみました。以下が実行結果です。 Start. [main] INFO org.hibernate.cfg.Environment - Hibernate 3.1 [main] INFO org.hibernate.cfg.Configuration - Reading mappings from resource: nu/mine/kino/models/Messages.hbm.xml [main] INFO org.hibernate.cfg.HbmBinder - Mapping class: nu.mine.kino.models.Messages -> MESSAGES [main] INFO org.hibernate.cfg.HbmBinder - Mapping collection: nu.mine.kino.models.Messages.messageses -> MESSAGES [main] INFO org.hibernate.cfg.SettingsFactory - RDBMS: MySQL, version: 5.0.22-standard-log [main] INFO org.hibernate.dialect.Dialect - Using dialect: org.hibernate.dialect.MySQLInnoDBDialect End. トラ1開始 [Thread-0] DEBUG org.hibernate.SQL - select messages0_.MESSAGE_ID as MESSAGE1_0_0_, messages0_.VERSION as VERSION0_0_, messages0_.NEXT_MESSAGE_ID as NEXT3_0_0_, messages0_.MESSAGE_TEXT as MESSAGE4_0_0_ from test.MESSAGES messages0_ where messages0_.MESSAGE_ID=? Thread2:1151250136968 トラ2開始 [Thread-1] DEBUG org.hibernate.SQL - select messages0_.MESSAGE_ID as MESSAGE1_0_0_, messages0_.VERSION as VERSION0_0_, messages0_.NEXT_MESSAGE_ID as NEXT3_0_0_, messages0_.MESSAGE_TEXT as MESSAGE4_0_0_ from test.MESSAGES messages0_ where messages0_.MESSAGE_ID=? Thread2:1151250136968 [Thread-1] DEBUG org.hibernate.SQL - update test.MESSAGES set VERSION=?, NEXT_MESSAGE_ID=?, MESSAGE_TEXT=? where MESSAGE_ID=? and VERSION=? トラ2終了 [Thread-0] DEBUG org.hibernate.SQL - update test.MESSAGES set VERSION=?, NEXT_MESSAGE_ID=?, MESSAGE_TEXT=? where MESSAGE_ID=? and VERSION=? Exception in thread "Thread-0" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [nu.mine.kino.models.Messages#1] at LockSample$1.run(LockSample.java:38) at java.lang.Thread.run(Unknown Source) トラ1のスレッドが更新かけてcommitしようとしたときに Exception in thread "Thread-0" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction という例外が発生しました。トラ2の処理で自動的にVERSIONカラムがインクリメントされて、トラ1のコミット時にそれを例外で通知してくれるということがわかりました。 この記事は 現在のアクセス:40401 |