You need to sign in to do that
Don't have an account?
select for update による排他のテストについて
お世話になっております。
Select for update を使用して行ロックをしたいと思い、その際のテストコードの書き方に苦慮しております。
ネットを検索したところ、http://www.tgerm.com/2011/04/visualizing-record-locking-in-soql-ie.html に良さそうなサンプルがあり、試してみたのですが結果は予想と異なるものでした。
以下、テストしたコードです。
public class TestRecordLocking {
testmethod public static void testLocking() {
Account accoutU1 = new Account(Name = 'Abhinav Gupta');
insert accoutU1;
accoutU1 = [Select Id, Name, Website from Account where Id =:accoutU1.id for update];
accoutU1.website = 'www.salesforce.com';
User u2 = [Select Id from user where id !=:UserInfo.getUserId() limit 1];
System.runAs(u2) {
Account accountU2 = [Select Id, Name, Website from Account where Id =:accoutU1.id ];
accountU2.WebSite = 'www.yahoo.com';
update accountU2;
system.debug('####accountU2####'+accountU2.WebSite); //#1
}
update accoutU1;
system.debug('####accountU1####'+accoutU1.website); //#2
}
testmethod public static void testLocking2() {
Account accoutU1 = new Account(Name = 'Abhinav Gupta');
insert accoutU1;
accoutU1 = [Select Id, Name, Website from Account where Id =:accoutU1.id for update];
accoutU1.website = 'www.salesforce.com';
User u2 = [Select Id from user where id !=:UserInfo.getUserId() limit 1];
System.runAs(u2) {
Account accountU2 = [Select Id, Name, Website from Account where Id =:accoutU1.id ];
delete accountU2; //#3
}
update accoutU1; //#4
}
}
testLocking()では、行ロックをかけたレコードに、runAs()で別のユーザーとしてアップデートをかけています。
私は、アップデート時に例外が発生すると思ったのですが、普通にアップデートがかかり(#1)、その後元の
ユーザーで上書きアップデート(#2)されています。
また、testLocking2()では、別のユーザーでdeleteさせてみたのですが、これも#3の部分でレコードが削除され、
#4で例外が発生します。
これは、Select for update が効いていないということなのでしょうか?あるいはrunAsの使い方に問題が
あるのでしょうか?
どうか、ご教授願います。宜しくお願いします。
自己レスです。
先ほどの質問に記載したリンク先でも、テストはうまくいっていなかったようです。
なので、うまくいかないことはわかりました。ただ、どうすればうまくいくのかは記載がありませんでした。
そこでぜひ、行ロックのテストをみなさんどのようにされているのか、教えていただけませんでしょうか?
for Updateはあくまで行ロックなので、先に更新してるひとの処理(トランザクション)が終わるまで待ってから、
selectを開始するので、後から更新かけるひとの処理を中断させる機能まではないと思ってます。
ちょっとやりたいことと違うかもしれませんが、私は排他制御が必要な箇所はオブジェクトが暗黙で持っているLastModifiedDateを画面表示前に取得して、
コントローラのクラスメンバに設定しておき
更新をかける際にその値と更新対象のレコードのLastModifiedDateと一致するかをif文でチェックしています。
public String haitatime {get; set;}
・・・
画面表示データ取得時処理の中
haitatime = objectHoge.LastModifiedDate.format('yyyyMMddHHmmssSSS');
・・・
更新時の処理のなか
String lastModDate = objectHoge.LastModifiedDate.format('yyyyMMddHHmmssSSS');
if (!haitatime.equals(lastModDate)) {・・・排他エラーのメッセージ処理・・・ return null;}
その他、例えば更新することでフラグやステータスが「0:承認待ち」から「1:承認済み」に変わったりして、
もう誰かが「1:承認済み」にしたら更新できないですよっていう感じならば、
更新処理の中で、更新対象を検索する際にfor updateで行ロックしておいて、
そのあとで更新対象のステータスチェックをすればいいかなと思います。
List<Hoge__c> hoges = [select status, ・・・ from Hoge__c where ・・・ for update];
Hoge__c hoge = hoges.get(0);
if (hoge.status.equals(1)) {・・・もう承認済みですよのメッセージ処理・・・ return null;}
hoge.status = 1;
update hoge;
因みに、Hoge__c をリストで取得していますが、気にしないでください。
オブジェクト型のままで受け取るとよく検索結果0件のときにリストで受け取れや~っていうエラーがでてくるので
そうしてるのですが、よく解ってませんw
確かに見てるといけそうな気もしますが。
Account accountU2 = [Select Id, Name, Website from Account where Id =:accoutU1.id ];
一般的なDBなら、こっちでもfor updateを付けることで、U1のロックが外れるまで待つか、
for update nowait とすることで、待たずにエラーになるはずです。
(SFDCで、nowaitができるのか? どんなエラーになるか?、、、まだわかってませんが)
自分も調べているところで、ここに来ただけなので、ご容赦を。