ヒベルナ楽観ロックorg.ヒベルナ.StleObject Station異常
Hibernate楽観ロックは多くのデータバージョン(version)記録メカニズムに基づいて実現される。データバージョンとは、データのバージョン識別を追加することであり、データベーステーブルベースのバージョン解決策では、一般的にデータベーステーブルのためにバージョンフィールドを追加することにより実現される。
データを読み込むときは、このバージョン番号を一緒に読み出して、更新するときは、このバージョン番号に1を加算します。このとき、提出されたデータのバージョン番号をデータベーステーブルに対応付けて記録されている現在のバージョン情報と比較します。提出されたデータのバージョン番号がデータベーステーブルの現在のバージョン番号より大きい場合、更新します。
楽観ロックテスト:
Userクラス:
User.hbm.xmlマッピングファイル:
テストコード:
以上のコードを実行すると、下記の異常が発生します。
理由:
初めてのテストの後、データベーステーブルのid=1のレコードのname属性値を「AAA」に設定します。再度テストします。Hibernateのキャッシュ(ロードメソッドを呼び出すと、照会したオブジェクトがキャッシュされます。)は、トランザクションtx 2を実行するときにsql文が発行されません。つまり、データベーステーブルに対応するレコードのversion値がプラスされていないことを意味します。ユーザーのname属性を更新する場合は、ロードで得られたオブジェクトとの変化があるかどうかを考慮し、sql文を送る必要がありますか?Hbernate自体がsql文を出すのは間違いないですが、肝心なデータベースで衝突が発生します。上記のテストは全部「lazy=false」の条件で実行されます。「lazy=false」を取り除いて、デフォルトでは遅延負荷を採用しています。衝突問題が起こらないので、sql文は以下の通りです。
データを読み込むときは、このバージョン番号を一緒に読み出して、更新するときは、このバージョン番号に1を加算します。このとき、提出されたデータのバージョン番号をデータベーステーブルに対応付けて記録されている現在のバージョン情報と比較します。提出されたデータのバージョン番号がデータベーステーブルの現在のバージョン番号より大きい場合、更新します。
楽観ロックテスト:
Userクラス:
public class User implements Serializable{
private int id;
private String name;
private int version;
...
}
User.hbm.xmlマッピングファイル:
<hibernate-mapping>
<class name="po.User" table="t_user" lazy="false" optimistic-lock="version">
<id name="id">
<generator class="native" />
</id>
<version name="version" column="version" type="integer"/>
<property name="name" />
</class>
</hibernate-mapping>
テストコード:
Session session = sessionFactory.openSession();
Session session2 = sessionFactory.openSession();
User user = (User)session.load(User.class, new Integer(1));
User user2 = (User)session2.load(User.class, new Integer(1));
Transaction tx = session.beginTransaction();
Transaction tx2 = session2.beginTransaction();
user2.setName("AAA");
tx2.commit();
user.setName("BBB");
tx.commit();
以上のコードを実行すると、下記の異常が発生します。
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
上記のコードを再実行すると、異常はなく、データベースに記録を更新します。理由:
初めてのテストの後、データベーステーブルのid=1のレコードのname属性値を「AAA」に設定します。再度テストします。Hibernateのキャッシュ(ロードメソッドを呼び出すと、照会したオブジェクトがキャッシュされます。)は、トランザクションtx 2を実行するときにsql文が発行されません。つまり、データベーステーブルに対応するレコードのversion値がプラスされていないことを意味します。ユーザーのname属性を更新する場合は、ロードで得られたオブジェクトとの変化があるかどうかを考慮し、sql文を送る必要がありますか?Hbernate自体がsql文を出すのは間違いないですが、肝心なデータベースで衝突が発生します。上記のテストは全部「lazy=false」の条件で実行されます。「lazy=false」を取り除いて、デフォルトでは遅延負荷を採用しています。衝突問題が起こらないので、sql文は以下の通りです。
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.name as name0_0_ from t_user user0_ where user0_.id=?
Hibernate: update t_user set version=?, name=? where id=? and version=?
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.name as name0_0_ from t_user user0_ where user0_.id=?
Hibernate: update t_user set version=?, name=? where id=? and version=?
つまり、毎回検索更新です。ロード時に遅延負荷をかけて得られたのはプロキシオブジェクトだけです。userを設定すると、sql文クエリが発行されます。直後に更新文が更新されます。ですから、二回のselectクエリで得られたversionの値は違っています。衝突はありません。