|
この章では、CORBA サーバ アプリケーションにトランザクションを統合する方法について、Transactions University サンプル アプリケーションを例にして説明します。Transactions サンプル アプリケーションは、学生がコースのセットを登録するプロセスをカプセル化します。Transactions サンプル アプリケーションでは、CORBA サーバ アプリケーションをトランザクションに統合するためのすべての方法ではなく、トランザクションの振る舞いの 2 つのモデルを示すことによって、アプリケーション一般、特にオブジェクトの永続状態に対するトランザクションの振る舞いの影響を示します。
また、この章ではユーザ定義例外についても説明します。Transactions サンプル アプリケーションはユーザ定義例外を利用します。この例外は、クライアント アプリケーションに返すことができ、クライアントが開始したトランザクションのロールバックを発生させることが可能です。
Oracle Tuxedo システムでは、データベースのトランザクションが正確に完了すること、およびデータベースのトランザクションが高性能なトランザクションの ACID プロパティ (原子性、一貫性、独立性、および持続性) のすべてを備えることを保証する手段として、トランザクションが提供されています。つまり、永続ストレージに複数の書き込みオペレーションを実行する上での要件があるので、オペレーションの成功が保証されている必要があります。オペレーションが 1 つでも失敗すれば、一連のオペレーションの全体がロールバックされます。
一般に、トランザクションは次のリストで説明される状況に適しています。それぞれの状況は、Oracle Tuxedo システムでサポートされているトランザクション モデルをカプセル化しています。
たとえば、旅行代理店アプリケーションがあるとします。クライアント アプリケーションは、たとえばフランスのストラスブールからオーストラリアのアリス スプリングスまでなど、遠隔地への旅行を手配する必要があります。このような旅行では、複数の予約が必要になります。クライアント アプリケーションでは、旅程の各区分の予約が順番に行われます。たとえば、ストラスブールからパリ、パリからニューヨーク、ニューヨークからロサンゼルスの予約が順番に行われます。しかし、いずれかのフライトが予約できないとき、クライアント アプリケーションにはその時点で予約済みのほかのフライトをすべてキャンセルする方法が必要です。たとえば、クライアント アプリケーションでロサンゼルスからホノルルへの指定日のフライトを予約できなかったら、クライアント アプリケーションはその時点までに済ませたフライト予約をキャンセルする必要があります。
たとえば、インターネット ベースのオンライン ショッピング アプリケーションを検討してみます。クライアント アプリケーションのユーザがオンライン カタログを一覧して、複数の購入を選択します。ユーザは、購入したい商品をすべて選択したら、ボタンをクリックして購入を実行し、クレジット カード情報を入力します。クレジット カードの確認が失敗したら (たとえば、ユーザのクレジット カード情報が無効だった場合)、ショッピング アプリケーションでは、未決済の購入選択をすべてキャンセルしたり、会話で行われたすべての購入トランザクションをロールバックしたりできる必要があります。
たとえば、銀行取引アプリケーションがあるとします。クライアントは、窓口オブジェクトに対して振替オペレーションを呼び出します。振替オペレーションは、銀行データベースに対して以下の呼び出しを実行するために窓口オブジェクトを必要とします。
ここでは、CORBA サーバ アプリケーションでのトランザクションを設計および実装する方法について、Transactions University サンプル アプリケーションを例にして説明します。また、ここでは、Transactions サンプル アプリケーションの動作と、トランザクションを実装する際の設計上の考慮事項についても説明します。トランザクション全般の詳細については、「トランザクションの CORBA クライアントおよびサーバ アプリケーションへの統合」を参照してください。
Transactions サンプル アプリケーションは、学生がコースのセットを登録するプロセスを、トランザクションを利用してカプセル化します。このアプリケーションで使用されているトランザクション モデルは、前の節で説明したように、会話モデルと、単一の呼び出しがデータベースに対して複数のオペレーションを個別に行うモデルを組み合わせたものです。
この Transactions サンプル アプリケーションは、Security サンプル アプリケーションを基に以下の機能を追加したものです。
Transactions サンプル アプリケーションは、次の 2 とおりの方法でトランザクションをロールバックします。
したがって、Transactions サンプル アプリケーションでも、ユーザ定義の CORBA 例外を実装する方法が示されています。たとえば、学生が登録可能なコースの最大数を超えているコースに登録しようとすると、サーバ アプリケーションは、TooManyCredits 例外を返します。クライアント アプリケーションは、この例外を受け取ると、自動的にトランザクションをロールバックします。
学生を登録するプロセスを実装するために、Transactions サンプル アプリケーションは、以下の作業を実行します。
Registrar オブジェクトの register_for_courses() オペレーションは、登録の要求を処理するためにループを実行し、リストの各コースについて次の処理を繰り返す。register_for_courses() オペレーションは、クライアント アプリケーションにパラメータ NotRegisteredList を返す。このパラメータには、登録に失敗したコースのリストが含まれている。
NotRegisteredList の値が空の場合、クライアント アプリケーションはトランザクションをコミットします。
NotRegisteredList 値にコースが含まれていない場合、クライアント アプリケーションは、登録に成功したコースに対する登録プロセスを完了するかどうかを指定するよう学生に要求します。登録を完了するように選択した場合、クライアント アプリケーションはトランザクションをコミットします。登録を取り消すように選択した場合、クライアント アプリケーションはトランザクションをロールバックします。
Registrar オブジェクトは TooManyCredits 例外をクライアント アプリケーションに返し、クライアント アプリケーションはトランザクションの全体をロールバックする。
Transactions サンプル アプリケーションの基本設計原理は、コース登録を一度に 1 つではなくグループ単位で処理することです。この設計は、Registrar オブジェクトへのリモート呼び出しの数を最小にすることに役立ちます。
この設計の実装で、Transactions サンプル アプリケーションはトランザクションの使用の 1 モデルを示します。トランザクションの各モデルの詳細については、「Oracle Tuxedo システムでのトランザクションの概要」を参照してください。このモデルの特徴は、次のとおりです。
begin() オペレーションを呼び出してトランザクションを開始し、次に Registrar オブジェクト上で register_for_courses() オペレーションを呼び出す。
Registrar オブジェクトは、登録可能なコースについて学生を登録してから、登録のプロセスに失敗したコースのリストを返します。クライアント アプリケーションは、トランザクションをコミットするかロールバックするかを選択できます。トランザクションは、クライアント アプリケーションとサーバ アプリケーションの間の会話をカプセル化します。
register_for_courses() オペレーションは、University データベースの複数チェックを実行する。いずれか 1 つのチェックが失敗した場合、トランザクションをロールバックできる。
Transactions University サンプル アプリケーションはトランザクションに関与するため、University サーバ アプリケーションは通常、オブジェクト状態に関していくつかの点 (特にロールバック時の) を考慮する必要があります。ロールバックが発生した場合、サーバ アプリケーションは、関連するすべてのオブジェクトが、正しい状態に復元された永続状態を持つことを保証する必要があります。
Registrar オブジェクトがデータベースのトランザクションに使用されるので、このオブジェクトについてはトランザクションに関与させるのが正しい設計上の選択です。つまり、このオブジェクトのインタフェースに always トランザクション ポリシーを割り当てることです。オブジェクト呼び出し時にトランザクションがまだスコープ指定されていない場合、Oracle Tuxedo システムは、トランザクションを自動的に開始します。
Registrar オブジェクトを自動的にトランザクション モードになるようにすることで、このオブジェクトによって実行されるすべてのデータベース書き込みオペレーションは、常にトランザクションのスコープ内で行われることになり、クライアント アプリケーションによってトランザクションが開始されたかどうかは関係がなくなります。サーバ アプリケーションは XA リソース マネージャを使用し、またオブジェクトは、データベースに書き込みを実行するときにトランザクションにあることが保証されます。したがって、XA リソース マネージャがオブジェクトの代わりにロールバックまたはコミットの権限を持つことになるので、オブジェクトにはロールバックまたはコミット権限がありません。
しかし、RegistrarFactory オブジェクトは、トランザクションの過程で使用されるデータを管理しないのでトランザクションから除外してもかまいません。オブジェクトをトランザクションから除外することで、トランザクションに課せられる処理のオーバーヘッドを最小化します。
Registrar オブジェクトをトランザクションに関与するようにするために、ICF ファイルは Registrar インタフェースに always トランザクション ポリシーを指定します。したがって、Transactions サンプル アプリケーションでは、ICF ファイルで、Registrar インタフェースに対して以下のオブジェクト ポリシーを指定します。
RegistrarFactory オブジェクトをトランザクションから除外するために、ICF ファイルは Registrar インタフェースに ignore トランザクション ポリシーを指定します。したがって、Transactions サンプル アプリケーションでは、ICF ファイルで、RegistrarFactory インタフェースに対して以下のオブジェクト ポリシーを指定します。
Transactions サンプル アプリケーションは、オブジェクト状態データを自動的に処理する Oracle トランザクション マネージャ サーバ (TMS) を使用します。XA リソース マネージャを使用すると、サーバ アプリケーションで管理される別のオブジェクトが、データベースとの間のデータの読み書きをどのように実行するかについて、以下のような特定の要件が適用されます。
CourseSynopsisEnumerator オブジェクトは、データベースからの読み取りを行うので、トランザクション内にスコープ指定される必要がある。
XA リソース マネージャの特徴は、ロールバック時のオブジェクト状態データの処理に関する設計の問題をより簡単なものにすることです。トランザクションに関与するオブジェクトは、コミットおよびロールバック権限を XA リソース マネージャに委譲します。これによって、サーバ アプリケーションの実装は、大幅に単純化されます。
University サンプル アプリケーションは、Oracle のトランザクション マネージャ サーバ (TMS) を使用します。Oracle のデータベースを使用するには、サーバ アプリケーションを構築する際に、Oracle から提供された特定のファイルを含める必要があります。
Transactions サンプル アプリケーションの構築、設定、および実行の詳細については、『Tuxedo CORBA University サンプル アプリケーション』を参照してください。また、オンライン マニュアルにも、各サンプル アプリケーション用の UBBCONFIG ファイルと、ファイルの各エントリの説明が示されています。
Oracle Tuxedo システムは、次のようにしてトランザクションをサポートします。
rollback_only() オペレーションを呼び出して、トランザクションをロールバック オンリーとしてマークすることができます。これによって、現在のトランザクションがコミットされるのを防ぐことができます。エンティティ (通常はデータベース) が破損データまたは不正確なデータで更新される危険がある場合、オブジェクトは、トランザクションを rollback としてマークしなければならない場合があります。Tobj_ServantBase::deactivate_object() オペレーションを呼び出して reason 値を渡すことを意味します。
オブジェクトがポーリングされるとき、そのオブジェクトが TransactionCurrent オブジェクトの rollback_only() オペレーションを呼び出して現在のトランザクションのコミットを拒否する可能性があります。さらに、現在のトランザクションがロールバックされる場合、オブジェクトは、データベースへの書き込みをスキップできます。現在のトランザクションのコミットを拒否するオブジェクトがなければ、トランザクションはコミットされます。
以後の節では、必要なトランザクションの振る舞いをオブジェクトに指定するためにオブジェクトのアクティブ化ポリシーとトランザクション ポリシーを使用する方法を説明します。これらのポリシーは、インタフェースに適用されます。したがって、そのインタフェースを実装しているすべてのオペレーションとオブジェクトに適用されます。
| 注意 : | サーバ アプリケーションが、トランザクションに参加するオブジェクトを管理している場合、そのアプリケーションの Server オブジェクトは、TP::open_xa_rm() オペレーションおよび TP::close_xa_rm() オペレーションを呼び出す必要があります。データベース接続の詳細については、「XA リソース マネージャのオープン」を参照してください。 |
Oracle Tuxedo システムは、always トランザクション ポリシーを提供します。これは、オブジェクトが呼び出されたときにトランザクションがまだスコープ指定されていない場合、Oracle Tuxedo システムがトランザクションを自動的に開始するように、そのオブジェクトのインタフェースを定義します。そのオブジェクトの呼び出しが完了すると、Oracle Tuxedo システムは、自動的にトランザクションをコミットまたはロールバックします。サーバ アプリケーションもオブジェクト実装も、この状態で TransactionCurrent オブジェクトを呼び出す必要はありません。つまり、Oracle Tuxedo システムは、サーバ アプリケーションの代わりに自動的に TransactionCurrent オブジェクトを呼び出します。
オブジェクトのインタフェースに always トランザクション ポリシーを割り当てることが適切なのは、次の場合です。
オブジェクトを自動的にトランザクションに関与させる必要がある場合は、当該のオブジェクトのインタフェースに関する次のポリシーを実装コンフィグレーション ファイル (ICF ファイル) に記述します。
| 注意 : | データベース カーソルは、複数のトランザクションにまたがることができません。CORBA の University サンプル アプリケーションにある CourseSynopsisEnumerator オブジェクトでは、一致するコース概要を University データベースで検索するためにデータベース カーソルが使用されています。データベース カーソルは複数のトランザクションにまたがることができないので、CourseSynopsisEnumerator オブジェクトの activate_object() オペレーションは一致するすべてのコース概要をメモリに読み取ります。カーソルはイテレータ クラスによって管理されているので、CourseSynopsisEnumerator オブジェクトからは見えないことに注意してください。 |
オブジェクトをトランザクションのスコープ内で呼び出すことができるようにする必要がある場合、optional トランザクション ポリシーをそのオブジェクトのインタフェースに割り当てることができます。optional トランザクション ポリシーは、データベース書き込みオペレーションを実行しないものの、トランザクション時に呼び出すことができるようにする必要があるオブジェクトに適しています。
オブジェクトに optional トランザクション ポリシーを適用するために、そのオブジェクトのインタフェース用の ICF ファイルで次のポリシーを指定できます。
オブジェクトがデータベース書き込みオペレーションを実行しており、オブジェクトがトランザクションに関与できるようにする必要がある場合は、always トランザクション ポリシーを割り当てる方が適切です。ただし、目的に応じて optional ポリシーを使用し、TransactionCurrent オブジェクトに対する呼び出しで書き込みオペレーションをカプセル化できます。つまり、オブジェクトがまだトランザクション内にスコープ指定されていない場合、データを書き込むオペレーション内で、トランザクションを開始およびコミットまたはロールバックするために TransactionCurrent オブジェクトを呼び出し、write 文の周囲にトランザクションをスコープします。これによって、データベース書き込みオペレーションがトランザクションに関与する形で処理されます。また、性能も向上します。TransactionCurrent オブジェクトがトランザクションのスコープ内で呼び出されなければ、すべてのデータベース読み取りオペレーションがトランザクションの外部になるので、効率が高くなります。
| 注意 : | Oracle Tuxedo システムで使用される XA リソース マネージャの一部では、トランザクションに関与するすべてのオブジェクトについて、データベース書き込みオペレーションに加えて読み取りオペレーションについても、トランザクション内でスコープ指定する必要があります (しかし、それでも独自にトランザクションのスコープ指定はできます)。たとえば、Oracle Tuxedo システムで Oracle TMS を使用する場合に、この要件が該当します。トランザクション ポリシーを選択してオブジェクトに割り当てる場合、使用している XA リソース マネージャの要件を把握します。 |
多くの場合、オブジェクトをトランザクションから除外することは危険です。このようなオブジェクトがトランザクション時に呼び出されると、オブジェクトは例外を返し、トランザクションがロールバックされることがあります。Oracle Tuxedo システムには never トランザクション ポリシーが用意されていて、これをオブジェクトのインタフェースに割り当てれば、現在のトランザクションが一時停止中でも、特定のオブジェクトをトランザクションの処理中に呼び出されないようにできます。
このトランザクション ポリシーは、ロールバックできないディスクに永続状態を書き込むオブジェクトに適しています。たとえば、XA リソース マネージャに管理されていないディスクにデータを書き込むオブジェクトなどです。クライアント アプリケーションで、呼び出しの一部がトランザクションのスコープ指定を引き起こしているかどうかを認識できない場合、クライアント/サーバ アプリケーションでこの機能を使用することは重要です。したがって、トランザクションがスコープ指定されている場合、このポリシーを持つオブジェクトが呼び出されると、トランザクションをロールバックできるようになります。
トランザクションのスコープ指定がされているときにオブジェクトの呼び出しを禁止するには、ICF ファイルで当該のオブジェクトのインタフェースに次のポリシーを割り当てます。
トランザクションの過程でオブジェクトの呼び出しを許可し、ただしそのオブジェクトをトランザクションの一部にはしないことがふさわしい場合もあります。このようなオブジェクトがトランザクションの最中に呼び出された場合、トランザクションは自動的に中断します。オブジェクトに対する呼び出しが完了すると、トランザクションは自動的に再開します。この目的のために、Oracle Tuxedo システムには ignore トランザクション ポリシーが用意されています。
ignore トランザクション ポリシーは、通常はデータをディスクに書き込まないファクトリなどのオブジェクトに適している場合があります。ファクトリをトランザクションから除外することで、そのファクトリは、トランザクションの最中でもほかのクライアントの呼び出しに使用できるようになります。さらに、このポリシーを使用すると、トランザクションに関与しているオブジェクトを呼び出す際のオーバーヘッドが軽減されるので、サーバ アプリケーションの処理効率が向上します。
トランザクションがオブジェクトに伝達されることを禁止するには、ICF ファイルで当該のオブジェクトのインタフェースに次のポリシーを割り当てます。
ICF ファイルを作成してオブジェクトにポリシーを指定する方法の詳細については、「ステップ 4: メモリ内でのオブジェクトの振る舞いの定義」を参照してください。
オブジェクトのインタフェースに always または optional トランザクション ポリシーが適用されている場合、Server オブジェクトの Server::initialize() オペレーションの TP::open_xa_rm() オペレーションを呼び出す必要があります。リソース マネージャは、UBBCONFIG ファイルの GROUPS セクションにある OPENINFO パラメータで提供された情報を基にオープンされます。デフォルト バージョンの Server::initialize() オペレーションは、自動的にリソース マネージャをオープンします。
データをディスクに書き込まず、トランザクションに参加しているオブジェクト (通常、トランザクション ポリシーは optional) がある場合、TP::open_xa_rm() オペレーションへの呼び出しを含める必要があります。その呼び出しでは、NULL リソース マネージャを指定します。
Server オブジェクトの Server::initialize() オペレーションが、XA リソース マネージャをオープンする場合は、Server::release() オペレーションに以下の呼び出しを含めます。
TP::close_xa_rm();
CORBA クライアントおよびサーバ アプリケーションにトランザクションが必要な場合に、トランザクションとオブジェクト状態の管理を統合する方法は複数あります。一般に、Oracle Tuxedo システムでは、オペレーション呼び出しの間のトランザクションについてスコープ指定を自動的に行うようにでき、このときにアプリケーションのロジックを変更したり、オブジェクトが永続状態をディスクに書き込む方法を変更したりする必要はありません。
ここでは、トランザクションとオブジェクト状態の管理に関する重要な項目の一部を説明します。
XA リソース マネージャを使用すると、たとえば CORBA University サンプル アプリケーションで使用される Oracle のリソース マネージャなどであれば、一般に、ロールバックでのオブジェクト状態データの処理に関連する設計上の問題が簡単になります。トランザクション オブジェクトはコミットおよびロールバックの処理をいつでも XA リソース マネージャに委譲できるので、このことによってサーバ アプリケーションを実装する作業が大幅に簡略化されます。つまり、トランザクションに関与するプロセス バウンドまたはメソッド バウンド オブジェクトは、トランザクション時にデータベースに書き込みを実行し、トランザクションのロールバック時にリソース マネージャに従ってデータベースに書き込まれたデータをロールバックできます。
transaction アクティブ化ポリシーは、トランザクションの作業が完了するまで書き込みたくない、または書き込めないメモリ内の状態をディスクに保持するオブジェクトに適しています。transaction アクティブ化ポリシーをオブジェクトに割り当てると、オブジェクトは、以下のようになります。
トランザクションの作業が完了したら、Oracle Tuxedo システムは各トランザクション バウンド オブジェクトの Tobj_ServantBase::deactivate_object() オペレーションを呼び出して、DR_TRANS_COMMITTING または DR_TRANS_ABORT のどちらかの reason コードを渡します。変数が DR_TRANS_COMMITTING の場合、オブジェクトは、データベース書き込みオペレーションを呼び出すことができます。変数が DR_TRANS_ABORT であれば、オブジェクトは自身のデータベース書き込みオペレーションを省略します。
transaction アクティブ化ポリシーのオブジェクトへの割り当ては、以下のような場合に適しています。
ロールバックの対象となる可能性のあるデータベース書き込みオペレーションの数を減らすことができるので、これによって、パフォーマンスがより効率的になります。
Oracle Tuxedo システムが reason 値として DR_TRANS_COMMITTING を渡す場合、オブジェクトは、必要であれば TransactionCurrent オブジェクトの rollback_only() オペレーションを呼び出すことができます。Tobj_ServantBase::deactivate_object() オペレーションの内部で rollback_only() オペレーションを呼び出した場合、その Tobj_ServantBase::deactivate_object() オペレーションが再度呼び出される点に注意してください。
トランザクションのコミットを待機してからデータベースに書き込む機能をオブジェクトに付与するには、当該のオブジェクトのインタフェースに関する次のポリシーを ICF ファイルに記述します。
| 注意 : | トランザクション バウンド オブジェクトは、Tobj_ServantBase::deactivate_object() オペレーション内でトランザクションを開始したり、ほかのオブジェクトを呼び出したりすることはできません。トランザクション バウンド オブジェクトが Tobj_ServantBase::deactivate_object() オペレーションの内部から実行できる唯一の有効な呼び出しは、データベースへの書き込みオペレーションのみです。 |
| 注意 : | また、トランザクションに関与するオブジェクトがある場合、そのオブジェクトを管理する Server オブジェクトは、管理されるオブジェクトがディスクに一切のデータを書き込まない場合でも、XA リソース マネージャをオープンするための呼び出し、およびクローズするための呼び出しをそれぞれ含んでいる必要があります (データをディスクに書き込まない、トランザクションに関与するオブジェクトがある場合、NULL リソース マネージャを指定します)。XA リソース マネージャをオープンする方法およびクローズする方法の詳細については、「XA リソース マネージャのオープン」および「XA リソース マネージャのクローズ」を参照してください。 |
CORBA クライアント/サーバ アプリケーションにトランザクションを統合する際の注意事項は、以下のとおりです。
CORBA::OBJ_ADAPTER
トランザクション内にあるクライアントが、現在別のトランザクション内にあるオブジェクトに対してオペレーションを呼び出そうとすると、以下のエラー メッセージが発行されます。
CORBA::INVALID_TRANSACTION
Tobj_ServantBase::deactivate_object() オペレーションで行うことを検討すること。これにより、Tobj_ServantBase::deactivate_object() オペレーションが呼び出された時点でトランザクションの結果が分かるので、オブジェクトが自身の状態を適切に扱うことが簡単になります。always を割り当てたオブジェクトが、クライアント アプリケーションではなく Oracle Tuxedo システムで開始されたトランザクションに関与している場合は、以下に注意してください。
オブジェクトのオペレーションの内部で例外が発生した場合、クライアント アプリケーションは OBJ_ADAPTER 例外を受け取ります。この状況で、Oracle Tuxedo システムは自動的にトランザクションをロールバックします。ただし、クライアント アプリケーションは、トランザクションが Oracle Tuxedo ドメインでスコープ指定されていないことをまったく認識していません。
| 注意 : | WebLogic Enterprise バージョン 4.2 ソフトウェアでは、この状況を回避する方法がありません。トランザクションを開始する前に、アプリケーションができるだけ周到にデータの検証を実行するようにしてください。 |
Transactions サンプル アプリケーションには、ユーザ定義例外 TooManyCredits のインスタンスが含まれています。クライアント アプリケーションが学生をコースに登録しようとしたときに、学生が登録可能なコースの最大数を超えている場合、サーバ アプリケーションはこの例外を返します。クライアント アプリケーションは、この例外を受け取ると、学生をコースに登録するトランザクションをロールバックします。ここでは、CORBA クライアント/サーバ アプリケーションでユーザ定義例外を定義および実装する方法について、TooManyCredits を例にして説明します。
CORBA クライアント/サーバ アプリケーションにユーザ定義例外を含める作業には、次の手順が関与します。
クライアント/サーバ アプリケーションの OMG IDL ファイルでは、以下の作業を行います。
TooManyCredits 例外は、学生が登録できる単位の最大数を表す short 型の整数値を渡すために定義します。したがって、TooManyCredits 例外の定義には、以下の OMG IDL 文が含まれます。exception TooManyCredits
{
unsigned short maximum_credits;
};
Registrar インタフェースの register_for_courses() オペレーションに対する OMG IDL 文を示したものです。NotRegisteredList register_for_courses(
in StudentId student,
in CourseNumberList courses)
raises (TooManyCredits);
例外を使用するオペレーションの実装では、次の例のように、例外を送出するコードを記述します。
if ( ... ) {
UniversityZ::TooManyCredits e;
e.maximum_credits = 18;
throw e;
}
|