投稿者:Tetsuo Ajima | 投稿日:2010年9月02日(木) 02:45

開発の現場から良く出る質問にトランザクション制御関連が多いのでまとめてみました。


トランザクションスコープ

基本的に、Apexコードの呼び出しから終了までのすべての処理は1つのトランザクションにつながって実行されます。処理開始とともにトランザクションが開始され、処理終了時にコミットされます。コード中での明示的なBegin/Commitはありません。実行時例外が投げられるとトランザクションはロールバックされます。これはJavaEEの宣言的トランザクション仕様のRequired属性に似た動作と言えます。
Visualforce Controllerの場合は、Webクライアントからのリクエストを受けてからレスポンスを返すまでが1トランザクションとなります。Apexトリガの場合はトリガの起動から終了までが1トランザクションです。トリガ内で発行されたDMLによって起動したトリガもすべて同一トランザクションに組み込まれます。


SavepointとRollback

コード中で明示的に、ある時点までのロールバック処理を実行できます。 Database.setSavepoint()メソッドを使ってSavepointを設定し、Database.rollback(Savepoint sp)メソッドを呼ぶと指定したSavepointまでロールバックされます。ガバナ制限により、Savepointは1トランザクションにつき5つまで設定でき、ロールバック処理は1トランザクションにつき20回まで行えます。


// Savepointの作成
Savepoint sp = Database.setSavepoint();

・・・Database更新処理等・・・

// Savepoint設定時の状態までロールバック
Database.rollback(sp);


SELECT FOR UPDATE

Apexコード中で実行するSOQLクエリは、"for update"キーワードを付けて行ロックを取得することができます。行ロックはトランザクションがコミットまたはロールバックされるまで持続されます。

[Select Id From Account for update]


非同期処理

@futureアノテーションを付与したメソッドは別トランザクションとして実行されます。ガバナ制限も別計算になります。同一トランザクションではガバナ制限に抵触するような処理も、このアノテーションで処理を非同期化・別トランザクション化することにより実現できる場合があります。このメソッドの呼び出し時に行われるのはキューイングのみで、実際の実行タイミングは保証されないことに注意してください。また、呼び出し元のトランザクションが失敗すると呼び出し先の処理もコミットされません。
@futureアノテーションについての詳細は下記を参照してください。
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_annotation.htm


スレッドのWait/Notify/Sleep

@futureアノテーション等で処理を別スレッド化することはできますが、スレッドを停止したり処理を待ったり等のスレッド制御はできません。
また、Apexにはsynchronizedキーワードがありません。原則的に、永続化されたデータ以外にスレッド間でデータを共有するという仕組みが無いため、synchronizedキーワードも不要となっています。


トランザクションの分離レベル(Isolation level)

更新したがまだコミットされていないデータは、他のトランザクションからは読み取られません。JavaEE仕様の"Read Committed"に似た動作です。あるトランザクションが更新中のレコードを他のトランザクションが読み取ろうとしたときは、更新中トランザクションが終了するまで一定時間待ちます。一定時間を過ぎた場合は待っている方のトランザクションが失敗します。
Force.comプラットフォームでは、トランザクション分離レベルを変更することはできません。


ロングトランザクションの排他制御

Webアプリケーションにおける「ロングトランザクション」とは、複数回のリクエストにわたってレコードの一貫性の保証や排他制御を行う概念です。
一般的な設計方針では、データ取得時からレコードの更新時までレコードのロックを取得する「悲観的(Pesimistic )ロック」と、レコードの更新時のみにレコードのロックを取得する「楽観的(Optimistic)ロック」があります。
Webアプリケーションでよく用いられるのは、楽観的ロックです。悲観的ロックは、ロックステータスの管理を自前で実装したりロックのタイムアウトを実装したりが必要で、実装が複雑化してしまうという技術的な問題があります。(業務要件としても楽観的ロックの方がマッチする場合も多くみられます。)

楽観的ロックの実装では「バージョンカラム」がよく使われます。バージョンカラムとは、例えばInteger型の項目を作成しておき、レコードの取得時とコミット直前でこの値が変わっていなければ、バージョンカラム項目の値をインクリメントしてレコードをコミットするというものです。こうしておけば、他のトランザクションによりレコードが更新されているかどうかをバージョンカラムの値が変わっているかどうかをチェックすることにより検出できます。

Force.comでは、わざわざバージョンカラムを実装せずに、SystemModstamp項目を利用できます。SystemModstamp項目は、標準オブジェクト/カスタムオブジェクトのすべてにデフォルトで作成されている日付時間型の項目です。この項目はレコードに対して何らかの更新操作が行われるとシステムにより値が更新されるので、データ取得時とコミット直前でこの値が変わっていなければ他のトランザクションによる更新が行われていないという判断の根拠となります。