9.5. エラー処理およびロールバック
トランザクションルートでは標準の Apache Camel エラー処理技術を使用できますが、例外とトランザクション境界との間の相互作用を理解することが重要です。特に、出力された例外が原因で通常、トランザクションのロールバックが発生することを考慮する必要があります。以下のトピックを参照してください。
9.5.1. トランザクションをロールバックする方法
以下の方法のいずれかを使用してトランザクションをロールバックできます。
9.5.1.1. ランタイム例外を使用したロールバックのトリガー
Spring トランザクションをロールバックする最も一般的な方法は、ランタイム (チェックされていない) 例外を出力することです。つまり、例外は java.lang.RuntimeException
のインスタンスまたはサブクラスです。java.lang.Error
タイプの Java エラーも、トランザクションロールバックをトリガーします。一方、チェックされた例外では、ロールバックはトリガーされません。
次の図は、Java エラーと例外がトランザクションにどのように影響するかをまとめたもので、ロールバックをトリガーするクラスは灰色で網掛けされています。
Spring フレームワークは、どの例外をロールバックするか、トリガーすべきでない XML アノテーションのシステムも提供します。詳細は、Spring Reference Guide の Rolling back を参照してください。
ランタイム例外がトランザクション内で処理される場合、つまり、例外がトランザクションの境界を設定するコードに浸透する前に、トランザクションはロールバックされません。詳細は、「デッドレターキューを定義する方法」 を参照してください。
9.5.1.2. rollback()
DSL コマンドの使用
トランザクションルートの途中でロールバックをトリガーする場合は、rollback()
DSL コマンドを呼び出してこれを実行できます。これにより、org.apache.camel.RollbackExchangeException
例外が発生します。つまり、rollback()
コマンドは、ランタイム例外を出力する標準的なアプローチを使用してロールバックをトリガーします。
たとえば、アカウントサービスアプリケーションで送金のサイズに絶対的な制限を設ける必要があると判断したとします。以下の例でコードを使用して、金額が 100 を超えるとロールバックをトリガーできます。
from("file:src/data?noop=true") .transacted() .bean("accountService","credit") .choice().when(xpath("/transaction/transfer[amount > 100]")) .rollback() .otherwise() .to("direct:txsmall"); from("direct:txsmall") .bean("accountService","debit") .bean("accountService","dumpTable") .to("file:target/messages/small");
前述のルートでロールバックをトリガーすると、無限ループにトラップされます。この理由は、rollback()
によって出力された RollbackExchangeException
例外がルートの開始時点で file
エンドポイントに伝播するためです。File コンポーネントには、例外が出力された交換を再送する信頼性機能が組み込まれています。もちろん、再送すると、交換によって別のロールバックがトリガーされ、無限ループが発生してしまいます。以下の例は、この無限ループを回避する方法を示しています。
9.5.1.3. markRollbackOnly()
DSL コマンドの使用
markRollbackOnly()
DSL コマンドを使用すると、例外を出力せずに現在のトランザクションを強制的にロールバックできます。これは、「rollback()
DSL コマンドの使用」 の例など、例外が発生したことで悪影響がある場合などに役立ちます。
以下の例は、rollback()
コマンドを markRollbackOnly()
コマンドに置き換えることで、「rollback()
DSL コマンドの使用」 の例を変更する方法を示しています。このバージョンのルートは無限ループの問題を解決します。この場合、送金金額が 100 を超えると、現在のトランザクションはロールバックされますが、例外は出力されません。file エンドポイントは例外を受信しないため、エクスチェンジを再試行せず、失敗したトランザクションは暗黙的に破棄されます。
次のコードは、markRollbackOnly()
コマンドで例外をロールバックします。
from("file:src/data?noop=true") .transacted() .bean("accountService","credit") .choice().when(xpath("/transaction/transfer[amount > 100]")) .markRollbackOnly() .otherwise() .to("direct:txsmall"); ...
ただし、前述のルート実装は理想的ではありません。ルートはトランザクションを (データベースの一貫性を保ちながら) クリーンにロールバックし、無限ループに入らないようにしますが、失敗したトランザクションの記録は保持しません。実際のアプリケーションでは、通常、失敗したトランザクションを追跡する必要があります。たとえば、トランザクションが成功しなかった理由を説明するために、関連の顧客に通知を送ることができます。失敗したトランザクションを追跡する便利な方法は、ルートにデッドレターキューを追加することです。
9.5.2. デッドレターキューを定義する方法
失敗したトランザクションを追跡するには、onException()
句を定義すると、関連するエクスチェンジオブジェクトをデッドレターキューに迂回できます。ただし、トランザクションのコンテキストで使用する場合は、例外処理とトランザクション処理の間に対話がある可能性があるため、onException()
句を定義する方法に注意する必要があります。次の例は、再出力された例外を抑制する必要があると仮定して、onException()
句を適切に定義する方法を示しています。
// Java import org.apache.camel.builder.RouteBuilder; public class MyRouteBuilder extends RouteBuilder { ... public void configure() { onException(IllegalArgumentException.class) .maximumRedeliveries(1) .handled(true) .to("file:target/messages?fileName=deadLetters.xml&fileExist=Append") .markRollbackOnly(); // NB: Must come *after* the dead letter endpoint. from("file:src/data?noop=true") .transacted() .bean("accountService","credit") .bean("accountService","debit") .bean("accountService","dumpTable") .to("file:target/messages"); } }
上記の例では、onException()
は IllegalArgumentException
例外をキャッチするよう設定され、問題のあるエクスチェンジをデッドレターファイル deadLetters.xml
に送信します。もちろん、この定義を変更して、アプリケーションで発生するあらゆる種類の例外をキャッチすることができます。例外の再出力動作とトランザクションのロールバック動作は、onException()
句の次の特別な設定によって制御されます。
-
handled(true)
- 再出力された例外を抑制します。この特定の例では、再出力された例外は、File エンドポイントに伝播するときに無限ループをトリガーするため、望ましくありません。「markRollbackOnly()
DSL コマンドの使用」を参照してください。ただし、場合によっては、例外を再出力できる場合があります (たとえば、ルートの開始時のエンドポイントが再試行機能を実装していない場合)。 -
markRollbackOnly()
- 例外を出力せずに、現在のトランザクションをロールバック用にマークします。この DSL コマンドは、エクスチェンジをデッドレターキューにルーティングするto()
コマンドの後に挿入する必要があることに注意してください。そうでない場合は、markRollbackOnly()
が処理のチェーンを割り込みするため、エクスチェンジはデッドレターキューに到達しません。
9.5.3. トランザクションに関する例外のキャッチ
onException()
を使用する代わりに、トランザクションルートで例外を処理する簡単な方法は、ルートに doTry()
および doCatch()
句を使用することです。たとえば、以下のコードは、無限ループに陥ることなく、トランザクションルートで IllegalArgumentException
をキャッチおよび処理する方法を示しています。
// Java import org.apache.camel.builder.RouteBuilder; public class MyRouteBuilder extends RouteBuilder { ... public void configure() { from("file:src/data?noop=true") .doTry() .to("direct:split") .doCatch(IllegalArgumentException.class) .to("file:target/messages?fileName=deadLetters.xml&fileExist=Append") .end(); from("direct:split") .transacted() .bean("accountService","credit") .bean("accountService","debit") .bean("accountService","dumpTable") .to("file:target/messages"); } }
この例では、ルートは 2 つのセグメントに分割されます。最初のセグメント (file:src/data
エンドポイントから) は受信エクスチェンジを受け取り、doTry()
および doCatch()
を使用して例外処理を実行します。2 つ目のセグメント (direct:split
エンドポイントから) は、すべてのトランザクション処理を実行します。このトランザクションセグメント内で例外が発生すると、最初に transacted()
コマンドに伝播され、それにより現在のトランザクションがロールバックされ、最初のルートセグメントの doCatch()
句によってキャッチされます。doCatch()
句は例外を再出力しないため、ファイルエンドポイントは再試行せず、無限ループが回避されます。