S2JDBCの楽観ロックのあれこれ

楽観ロックについては、色々とみたので、別エントリーで書いてみます。

まずはデータを挿入します。

<Script文=今までのテストコードも同じテーブルです>
CREATE TABLE "ORDER_VOUCHER"
( "ID" NUMBER(10,0) NOT NULL ENABLE,
"ACCOUNT_ID" VARCHAR2(10),
"ACCOUNT_NAME" VARCHAR2(30),
"VERSION" NUMBER(10,0) NOT NULL ENABLE,
CONSTRAINT "ORDER_VOUCHER_PK" PRIMARY KEY ("ID") ENABLE
)


@Entity
public class OrderVoucher {

/** idプロパティ */
@Id
@Column(precision = 10, nullable = false, unique = true)
public Long id;

/** accountIdプロパティ */
@Column(length = 10, nullable = true, unique = false)
public String accountId;

/** accountNameプロパティ */
@Column(length = 30, nullable = true, unique = false)
public String accountName;

/** versionプロパティ */
@Version
@Column(precision = 10, nullable = false, unique = false)
public Long version;
}

ServiceClass

public int insertAll(List entities) {
return this.jdbcManager.insertBatch(entities).execute();
}

テストコード

public void testInsertAll() throws Exception {
List entities = new ArrayList();
OrderVoucher entity = new OrderVoucher();
entity.id = Long.parseLong("1");
entity.accountId = "11";
entity.accountName = "222";
entity.version = Long.parseLong("0");
entities.add(entity);

entity = new OrderVoucher();
entity.id = Long.parseLong("2");
entity.accountId = "22";
entity.accountName = "333";
entities.add(entity);

int expecteds = orderVoucherService.insertAll(entities);
assertEquals(expecteds.length, 2);
}

VersionがNotNullが入っていますので、エラーになります。
この時、最初の1件は問題ないので、1件だけ挿入されるのかなぁ、と思いましたが、きちんとロールバックされ、データベースを確認した所、1件もデータが格納されていませんでした。

テストコード

public void testInsertAll() throws Exception {
List entities = new ArrayList();
OrderVoucher entity = new OrderVoucher();
entity.id = Long.parseLong("1");
entity.accountId = "11";
entity.accountName = "222";
entity.version = Long.parseLong("0");
entities.add(entity);

entity = new OrderVoucher();
entity.id = Long.parseLong("2");
entity.accountId = "22";
entity.accountName = "333";
entity.version = Long.parseLong("0");
entities.add(entity);

int[] expecteds = orderVoucherService.insertAll(entities);
assertEquals(expecteds.length, 2);
}

今度はうまくいきました。が、Versionは「0」を指定していましたが、「1」になっていました。仕様ですね、出力されたSQLをみると、一目瞭然です。


では、普通にS2AbstractServiceを使って、Updateします。

テストコード

public void testUpdate() throws Exception {
OrderVoucher entity = new OrderVoucher();
entity.id = Long.parseLong("1");
entity.accountName = "8888";

int expected = orderVoucherService.update(entity);
assertEquals(expected, 1);
}

怒られました。。。

エラー内容は、これ。
[ESSR0736]エンティティ(jp.globalsystems.businessflow.entity.OrderVoucher@272961)は既に変更されています。

SQLをみると、すぐに分かります。
DEBUG 2009-01-05 21:52:09,937 [main] update ORDER_VOUCHER set ACCOUNT_ID = null, ACCOUNT_NAME = '8888', VERSION = VERSION + 1 where ID = 1 and VERSION = null

Versionを何も指定していないからですね。
では、現在のテーブルにある「1」を指定してみます。

テストコード

public void testUpdate() throws Exception {
OrderVoucher entity = new OrderVoucher();
entity.id = Long.parseLong("1");
entity.accountName = "8888";
entity.version = Long.parseLong("1");

int expected = orderVoucherService.update(entity);
assertEquals(expected, 1);
}

成功しました。Versionも「2」になっています。

ここでVersionを「3」にUpdateします。つまり、誰かが更新した、という想定で。
そして、Versionを「2」に設定し、実行してみます。

テストコード

public void testUpdate() throws Exception {
OrderVoucher entity = new OrderVoucher();
entity.id = Long.parseLong("1");
entity.accountName = "7777";
entity.version = Long.parseLong("2");

int expected = orderVoucherService.update(entity);
assertEquals(expected, 1);
}

怒られました。想定通りです。

Throwable:org.seasar.extension.jdbc.exception.SOptimisticLockException:
[ESSR0736]エンティティ(jp.globalsystems.businessflow.entity.OrderVoucher@10dc656)は既に変更されています。

DEBUG 2009-01-05 22:07:41,312 [main] update ORDER_VOUCHER set ACCOUNT_ID = null, ACCOUNT_NAME = '7777', VERSION = VERSION + 1 where ID = 1 and VERSION = 2

つまり、
バージョンチェックはする
version属性の値は更新はする
ですね。これがデフォルトになります。


includesVersionというものが、本家サイトを見ると、ありますね。
同じテストソースにincludesVersionを入れて、実行してみます。

ServiceClass

public int updateOptimistic(OrderVoucher entity) {
return this.jdbcManager.update(entity).includesVersion().execute();
}

テストコード

public void testUpdateOptimistic() throws Exception {
OrderVoucher entity = new OrderVoucher();
entity.id = Long.parseLong("1");
entity.accountName = "7777";
entity.version = Long.parseLong("2");

int expected = orderVoucherService.updateOptimistic(entity);
assertEquals(expected, 1);
}


うまく行きました。Versionは「2」になります。

つまり、
バージョンチェックはしない
version属性の値は更新はする
ですね。


更新対象がない場合、例外を出力しない方法は以下の通りです。

ServiceClass

public int updateOptimisticException(OrderVoucher entity) {
return this.jdbcManager.update(entity).suppresOptimisticLockException().execute();
}

テストコード

public void testUpdateOptimisticException() throws Exception {
OrderVoucher entity = new OrderVoucher();
entity.id = Long.parseLong("1");
entity.accountName = "4444";
entity.version = Long.parseLong("2");

int expected = orderVoucherService.updateOptimisticException(entity);
assertEquals(expected, 1);
}

戻り値が0になります=対象がない場合は。
更新対象があった場合は、件数が戻ります(上記の場合ですと、1)。
Exceptionに飛ばさないで、0件、1件以上という分岐で処理をしたい場合、使えるのではないか、と思います。

S2JDBCでここまで処理をしてもらえるので、呼出す時にはきちんと設定すべき値は設定し、実行すれば、非常に使いやすいです。