[Salesforce] エラーが出てもエラーレコード以外は更新させる
salesforceでインサート・アップデートする場合、ガバナ制限の関係から都度DMLを発行せずにある程度まとめてやる事が多い。
その際に、一件でもエラーが出てしまうと他もまきこまれてロールバックされてしまうので、エラー以外は更新させる方法。
通常の方法
通常の方法、というか、簡易な方法としては、以下のようにインサートする。
1 2 3 4 5 | |
for文の中でinsertせずに、リストにまとめておいて外で一回だけinsertする。
これで、DMLは一回だけ発行したことになるので、節約が可能。
ループの中ではSOQL、DMLはしない。これがapexの鉄則。
しかしこれだと例えば全部で200件インサートする予定があって、そのうち150件目に不備がありエラーが出た場合、 そこで処理がストップし、それまでにインサートした149件もすべてロールバックされインサート自体がなかったことになる。 要件によってはこの方がいい場合もあるが、エラーが出たレコード以外は全てインサートしたい場合もある。
Database Class
そんな時は、Databaseクラスを使う。
使い方は、Database.insert()とするだけ。
このメソッドの第1引数に、インサートするオブジェクトのリストを、 第2引数に、エラー時の挙動を入れる。
- trueの場合はエラー時には全てロールバック(上記の単純な
insertと同じ)(デフォルト) - falseの場合はエラーレコード以外はインサートする
上記の例を書き換えると、以下のようになる。
1 2 3 4 5 | |
これでエラーのレコード以外はインサートさせる事が出来た。
updata、upsert、delete についても同じ。
Upsert
upsertも上記と方法は同じだが、upsertでは外部IDの指定が可能になるが、その方法が少し簡易バージョンとは違った。
例えば、Leadオブジェクトの、カスタム項目、UserID__cを外部IDとして指定する場合。
簡易バージョンの場合は以下のようになる。
1 2 3 | |
上記、Databaseを使ったパターンだと、ドキュメントには以下のように書かれている。
1
| |
この第二引数のexternalIdFieldが外部IDの指定箇所になるが、型は、Schema.SObjectFieldとなっていて、
そのまま Database.upsert(lists, UserID__c, false)とやるとエラーが出る。
なので以下のようにして指定したい項目のSchema.SObjectFieldを取得して指定してやる必要がある。
1 2 3 4 5 | |
これで外部IDを指定したアップサートが可能となる。
エラーハンドリング
さて、DMLでエラーが発生した場合、その内容を取得するには、try..catchで例外を拾っていた。
1 2 3 4 5 6 | |
しかし、Databaseを使ってinsertなどをした場合は別の方法で取得する。
というか、エラーだけでなく、結果を一件ずつ取得することが出来るため、それらからエラー分を取得する、という形になる。
SaveResultクラスを使用する。
1
| |
取得した結果から、成功、失敗を取得し、内容を取得したりする。
1 2 3 4 5 6 7 8 9 10 11 12 | |
例えば出力内容は以下のような感じ。
1 2 | |
この SaveResult は、インサート時の結果を取得するためのクラスで、
インサート、アップサート、アップサート、デリート、それぞれのクラスが別々に用意されている。
- insert – SaveResult
- update – UndeleteResult
- upsert – UpsertResult
- delete – DeleteResult
詳しくは、Apex 開発者ガイド、を参照。
(直接のリンクがなぜか貼れなかった)
Apex開発者ガイド – リファレンス – Database名前空間
まとめ
以上で一通り想定していた動作をさせることができた。
エラー時に他をロールバックするかどうかとかは、結構忘れがちになるけれど、結構難しい問題。
仕様策定時にきちんと考慮していきたい。