Doctrine2 トランザクションと並行性

Doctrine 2 ORM 2 documentation - 13. Transactions and Concurrency を翻訳してみようと思います。

13. トランザクションと並行性

13.1. トランザクションの境界設定

トランザクションの分割は、トランザクション境界を定義するタスクです。
適切なトランザクション分割は非常に重要です。これが適切に行われていない場合、アプリケーションのパフォーマンスに悪影響を与えることがあります。
多くのデータベースやPDOなどのデータベース抽象化レイヤは、デフォルトでは自動コミットモードで動作します。
これは、1つ1つのSQL文が小さなトランザクションでラップされていることを意味します。
トランザクションのコストは安くないので、明示的なトランザクション分割がないと、すぐにパフォーマンスが低下します。

Doctrine2はほとんどの場合、適切にトランザクション境界の面倒をみます。
EntityManager#flush()を実行するまで、INSERT/UPDATE/DELETEといったすべての書き込み操作は、単一トランザクションでその変更のすべてがラップ&キューイングされます。

ですが、トランザクション境界をDoctrine2まかせにせず、明示的に制御することも可能です(し、これを推奨します)。
Doctrine ORMを使用して、トランザクションを処理する2つの方法について詳細を説明します。

13.1.1. 方法1 - 暗黙的

最初の方法は、Doctrine ORM の エンティティ・マネージャ が提供する「暗黙的な」トランザクション処理を使う方法です。
明示的なトランザクション分割をしないコードスニペットは以下になります。

$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();

上記のコードでは、明示的なトランザクション分割をしていないので、EntityManager#flush()トランザクションを開始し、コミット/ロールバックします。
【ToDO 翻訳】This behavior is made possible by the aggregation of the DML operations by the Doctrine ORM and is sufficien if all the data manipulation that is part of a unit of work happens through the domain model and thus the ORM.

13.1.2. 方法2 - 明示的

明示的にトランザクション境界を制御する方法は、直接 Doctrine\DBAL\Connection API を使用することです。
コードは次のようになります。

$em->getConnection()->beginTransaction(); // suspend auto-commit
try {
    //... do some work
    $user = new User;
    $user->setName('George');
    $em->persist($user);
    $em->flush();
    $em->getConnection()->commit();
} catch (Exception $e) {
    $em->getConnection()->rollback();
    throw $e;
}

【ToDO翻訳】Explicit transaction demarcation is required when you want to include custom DBAL operations in a unit of work or when you want to make use of some methods of the EntityManager API that require an active transaction.
これらのメソッドは、TransactionRequiredException をスローします。

より便利な代替手段は、Connection#transactional($func)EntityManager#transactional($func) で提供される制御の抽象化を使用することです。
これらの抽象化制御を使う場合はコード量は減りますが、トランザクションロールバックすることを決して忘れないでください。
以下の例は、前述のコードと等価です。

$em->transactional(function($em) {
    //... do some work
    $user = new User;
    $user->setName('George');
    $em->persist($user);
});

Connection#transactional($func)EntityManager#transactional($func) の違いは、後者ではエンティティ・マネージャをフラッシュする前にトランザクションをコミットし、例外発生時にはロールバックします。

13.1.3. 例外処理

暗黙的なトランザクション分割を使用して EntityManager#flush() で例外が発生した場合、トランザクションは自動的にロールバックし、エンティティ・マネージャはクローズします。

【ToDO翻訳】When using explicit transaction demarcation and an exception occurs, the transaction should be rolled back immediately and the EntityManager closed by invoking EntityManager#close() and subsequently discarded, as demonstrated in the example above.
これは、前述の抽象化制御によってエレガントに処理することができます。
注意:例外を補足した場合、一般的には再スローする必要があります。
例外から回復する場合、手前のcatchブロック内で明示的に例外を補足します。(ただし、トランザクションロールバック&エンティティマネージャをクローズすることを忘れないでください。)
【ToDO翻訳】All other best practices of exception handling apply similarly (i.e. either log or re-throw, not both, etc.).

【ToDO翻訳】As a result of this procedure, all previously managed or removed instances of the EntityManager become detached.
デタッチされたオブジェクトの状態は、トランザクションロールバックされた時点の状態になります。
オブジェクトの状態はロールバックされているので、オブジェクトはデータベースと同期がとれている状態です。
アプリケーションは、【ToDO翻訳】knowing that their state is potentially no longer accurate. デタッチされたオブジェクトを使い続けることができます。

例外が発生した後に、別のunit of workを始める場合は、新しいエンティティマネージャで行う必要があります。

13.2. ロックのサポート

Doctrine2は、悲観的と楽観的ロックをネイティブでサポートしています。
これにより、アプリケーションにおいてエンティティために必要とされるロックの種類を、非常にきめ細かく制御することができます。

13.2.1. 楽観的ロック

データベーストランザクションは、単一の要求における同時実行制御のためのfineです。
【ToDO翻訳】ただし, データベーストランザクションはリクエストをまたがるべきではない、いわゆる "user think time".