エンティティ所有/被所有関係とトランザクション処理

2011年1月19日(水)
清野 克行

3.2. プログラム処理: サーバーコード

【リスト1】所有・被所有関係とトランザクション処理 ビーンズ・プログラム

{syntaxhighlighter brush:java} package jdoajax; import java.util.*; import com.google.App Engine.api.datastore.DatastoreService; import com.google.App Engine.api.datastore.DatastoreServiceFactory; import com.google.App Engine.api.datastore.Entity; import com.google.App Engine.api.datastore.EntityNotFoundException; import com.google.App Engine.api.datastore.Key; import com.google.App Engine.api.datastore.KeyFactory; import com.google.App Engine.api.datastore.FetchOptions; import com.google.App Engine.api.datastore.Query; import com.google.App Engine.api.datastore.Transaction; import com.google.App Engine.api.datastore.Query.FilterOperator; public class colBean { DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); : public String addOrd(Hashtable query){ Transaction tx = ds.beginTransaction(); // (A) try{ // (1)開始: オーダー・マスター登録・更新処理 Key omaskey = KeyFactory.createKey("ordMas", "omas001"); Entity omas = new Entity("ordMas", "omas001"); // (X) String kind =(String)query.get("kind"); Integer price = Integer.valueOf((String)query.get("price")); Integer ordno = 1; try{ Entity omasc = ds.get(tx, omaskey); // ① Integer ordamt = Integer.parseInt(omasc.getProperty("ordamt").toString()); // ② ordamt += price; // ② omas.setProperty("ordamt", ordamt); // ② ordno = Integer.parseInt(omasc.getProperty("lastord").toString()); // ③ ++ordno; //③ omas.setProperty("lastord", ordno); // ③ }catch(EntityNotFoundException e){ omas.setProperty("ordamt", price); // ④ omas.setProperty("lastord", ordno); // ④ } // (1)終了: オーダー・マスター登録・更新処理 // (2)開始: ショップ・マスター登録・更新処理 String shopno =(String)query.get("shopNo"); Key smaskey = KeyFactory.createKey(omaskey, "shopMas", shopno); Entity smas = new Entity("shopMas", shopno, omaskey); // (Y) smas.setProperty("shopno", shopno); try{ Entity smasc = ds.get(tx, smaskey); Integer shopamt = Integer.parseInt(smasc.getProperty("shopamt").toString()); shopamt += price; smas.setProperty("shopamt", shopamt); }catch(EntityNotFoundException e){ smas.setProperty("shopamt", price); } // (2)終了: ショップ・マスター登録・更新処理 // (3)開始: オーダートランザクション追加処理 Entity otran = new Entity("ordTran", ordno.toString(), smaskey); // (Z) otran.setProperty("shopno", shopno); otran.setProperty("kind", kind); otran.setProperty("type", (String)query.get("type")); otran.setProperty("itemNo", (String)query.get("itemNo")); otran.setProperty("itemName", (String)query.get("itemName")); otran.setProperty("price", price); if(kind.equals("desktop")){ otran.setProperty("cpu", (String)query.get("cpu")); otran.setProperty("memory", (String)query.get("memory")); otran.setProperty("os", (String)query.get("os")); }else if(kind.equals("display")){ otran.setProperty("size", (String)query.get("size")); }else if(kind.equals("printer")){ otran.setProperty("papersize", (String)query.get("papersize")); otran.setProperty("resolution", (String)query.get("resolution")); } Date ordDate = new Date(); otran.setProperty("orddate", ordDate); // (3)オーダートランザクション追加処理 ds.put(tx, omas); // (4)オーダー・マスター書きこみ ds.put(tx, smas); // (4)ショップ・マスター書き込み ds.put(tx, otran); // (4)オーダー・トランザクション書き込み tx.commit(); // (B) return "購入登録成功:"; }finally { if (tx.isActive()) { // (C) tx.rollback(); // (C) return "購入登録不成功: ロールバック"; } } } : : } {/syntaxhighlighter}

リスト1は、エンティティ間に図5のような所有・被所有関係を持たせ、複数のエンティティの更新・追加をトランザクション処理で行う例になっています。また、このように複数エンティティ対象とするトランザクション処理は、所有・被所有の関係を持つエンティティに限定されます。

[1] トランザクション処理

トランザクション処理では、(1)のbeginTransactionによってデータ・ストアに対するトランザクションが開始され、図8の構成を持つ、3種類のエンティティに対する更新・登録処理が行われます。

更新・登録処理が正常に終了した場合は、(B)のcommitメソッドによってコミットが実行され、処理内容が確定します。

Commitまでの処理が正常に終了しなかった場合は、トランザクションはアクティブ状態のまま(isActive)です。(C)のif文が成立してトランザクションはロールバック(rollback)され、beginTransactionからの処理内容は、すべて開始前の状態に戻されます。

[2] トランザクション内での処理内容

トランザクション内では、

  • (1)オーダー・マスター登録・更新処理
  • (2)ショップ・マスター登録・更新処理
  • (3)オーダー・トランザクション追加処理

の3種類のデータ・ストア・アクセス処理が、この順番で行われています。

(1)のオーダー・マスター登録・更新処理では、最初に①においてキー指定によってエンティティを取得します。トランザクション内でのデータ取得(get)では、第1引数に、(A)で取得したトランザクションのインスタンスを指定します・

  • Entity omasc = ds.get(tx, omaskey);

そのあと、オーダー・マスター(ordMas)の受注合計額(ordamt)を更新する処理と、オーダー番号(ordno)をインクリメント(+1)する処理を行っています。ただし、最初に受注登録するときには、エンティティはまだ存在せず、EntityNotFoundExceptionの例外が発生します。オーダー・マスターの初期データ設定は、そのcatch句内の④で行っています。

(2)と(3)のショップ・マスター登録・更新処理と、オーダー・トランザクション追加処理も、ほぼ同様の処理内容です。

(2)では、売上店(shopno)単位で売上金額(shopamt)を更新し、
(3)では、オーダーごとのトランザクション・データを、売上店単位で追加しています。

ここで、永続化用エンティティを作成する場合、(1)のオーダー・マスターのように親を持たない「ルート・エンティティ」では、コンストラクタ生成の引数指定で(X)、第1引数でkind名、第2引数でエンティティのキー名を指定しますが、(2)のショップ・マスター、(3)の受注トランザクションのように被所有エンティティを持つ場合には、このほかに(Y)、(Z)のように、第3引数で親キーを指定します

このように、Low-Level APIでは、所有・被所有関係をメソッドの引数で明確に指定できるため、分かりやすくなっています。一方、JDOを使う場合は、これに比べて分かりやすさに欠けます。JDOのサンプルは紹介しませんが、興味がある方は、筆者が書いた『Google App Engine for Java実践クラウド・プログラミング』などを参照してください。

以上の処理後、(4)で、ルート・エンティティのオーダー・マスターから順番に、データ・ストアへの書き込みを実行します。putメソッドの第1引数には、getの場合と同様に、トランザクションのインスタンスを指定します。

3.3. プログラム処理:クライアントコード

【リスト2】クライアント側プログラム

{syntaxhighlighter brush:xml} <!DOCTYPE html> <html> <head> <meta charset=utf-8> <title>コンピュータ商品マスター参照・購入</title> <link type="text/css" rel="stylesheet" href="/csslib/style.css"/> <script type="text/javascript" src="/dwr/interface/colBean.js"></script> <script type="text/javascript" src="/dwr/engine.js"></script> <script type="text/javascript" src="/dwr/util.js"></script> <script type="text/javascript" src="/jslib/jquery-1.4.2.min.js"></script> <script type="text/javascript"> //<![CDATA[ window.resizeTo(370,535); window.moveTo(10,10); var kind = "desktop"; var query = {}; var spectag1 = '<table width="330" border="1" bgcolor="#eeeeee">' + '<tr><th width="100">CPU</th>' + '<td><select id="cpu"><option value="">=CPU=</option></select></td></tr>' + '<tr><th>メモリ</th>' + '<td><select id="memory"><option value="">=メモリ=</option></select></td></tr>' + '<tr><th>OS</th>' + '<td><select id="os"><option value="">=OS=</option></select></td></tr>' + '<tr><th>ステータス</th><td id="status"></td></tr></table>'; var spectag2 = '<table width="330" border="1" bgcolor="#eeeeee">' + '<tr><th width="100">サイズ</th>' + '<td><select id="size"><option value="">=サイズ=</option></select></td></tr>' + '<tr><th>ステータス</th><td id="status"></td></tr></table>'; var spectag3 = '<table width="330" border="1" bgcolor="#eeeeee">' + '<tr><th width="100">用紙サイズ</th>' + '<td><select id="papersize"><option value="">用紙サイズ</option></select></td></tr>' + '<tr><th>解像度</th>' + '<td><select id="resolution"><option value="">解像度</option></select></td></tr>' + '<tr><th>ステータス</th><td id="status"></td></tr></table>'; $(function(){ $("#type").val("デスクトップPC"); $("#rev").click(function(e){ colBean.revItem($("#itemNo").val(), function(dat){ var items = dat.split("<i>"); var type = items[0]; $("#type").text(type); $("#itemName").text(items[1]); $("#price").text(items[2]); if(type=="デスクトップPC"){ kind = "desktop"; $("#spec").html(spectag1); var cpu = items[3].replace("["," ").replace("]"," ").split(","); DWRUtil.addOptions("cpu", cpu); var memory = items[4].replace("["," ").replace("]"," ").split(","); DWRUtil.addOptions("memory", memory); var os = items[5].replace("["," ").replace("]"," ").split(","); DWRUtil.addOptions("os", os); }else if(type=="液晶ディスプレイ"){ kind= "display"; $("#spec").html(spectag2); var size = items[3].replace("["," ").replace("]"," ").split(","); DWRUtil.addOptions("size", size); }else if(type=="プリンタ"){ kind = "printer"; $("#spec").html(spectag3); var papersize = items[3].replace("["," ").replace("]"," ").split(","); DWRUtil.addOptions("papersize", papersize); var resolution = items[4].replace("["," ").replace("]"," ").split(","); DWRUtil.addOptions("resolution", resolution); } }); }); // (1)オーダー情報登録: 開始 $("#ord").click(function(){ // ①「購入」ボタンクリックイベント query["kind"]=kind; query["itemNo"]=$("#itemNo").val(); query["type"]=$("#type").text(); query["itemName"]=$("#itemName").text(); query["price"]=$("#price").text(); query["shopNo"] = $("#shopNo").val(); if (kind == "desktop") { // ②デスクトップ固有項目セット query["cpu"] = $("#cpu").val(); query["memory"] = $("#memory").val(); query["os"] = $("#os").val(); }else if (kind == "display") { // ③ディスプレイ固有項目セット query["size"] = $("#size").val(); }else if(kind == "printer"){ // ④プリンタ固有項目セット query["papersize"] = $("#papersize").val(); query["resolution"] = $("#resolution").val(); } colBean.addOrd(query, function(stat){ // ⑤オーダー情報サーバー送信 $("#status").text(stat); }); }); // (1)オーダー情報登録:終了 }); //]]> </script> </head> <body bgcolor="#cccccc"> <h2 style="color: #aa0022">コンピュータ商品参照・購入</h2> <h3>PCショップ秋葉原    <input type="button" id="rev" value=" 参照 "/> <input type="button" id="ord" value=" 購入 "/> </h3> <div><input type="hidden" id="shopNo" value="shibuya"/></div> <table width="330" border="1" bgcolor="#eeeeee"> <tr><th id="stitle" colspan="2">商品情報</th></tr> <tr> <th width="100">製品番号</th> <td><input type="text" size="12" id="itemNo"/></td> </tr> <tr><th>タイプ</th><td id="type"></td></tr> <tr><th>製品名</th><td id="itemName"></td></tr> <tr><th>価格</th><td id="price"></td></tr> </table> <nobr id="spec"></nobr> </body> </html> {/syntaxhighlighter}

クライアント側のプログラムは、商品情報の表示処理に関しては、これまでと同じです。新しい部分は、(1)の部分で行われるオーダー情報登録処理です。画面上の「購入」ボタンをクリックすると、①の匿名関数が呼び出されます。

関数内では、画面上に表示・セットされた項目値を、連想配列queryにセットしていきます。商品種類によって異なる項目については、①~③で個別にセットしています。

データ項目をセット後、queryを引数指定して、⑤のDWRでのクラス・メソッド・スタイルでサーバーへにリクエストを送信すれば、クライアント側での処理は終了です。

以上、ここまで受注処理を例にとって、Low-Level APIのプログラム・サンプルを見てきました。App Engineのサンプルとしては、ちょっと変わっていると思われたかもしれません。しかし、2010年12月2日にリリースされたバージョン1.4では、ビジネス・アプリケーションにとって有効な機能変更・強化がいくつも行われており、また2011年にはApp Engine for Businessのリリースが予定されています。ビジネス・アプリケーションにおけるApp Engineの利用が普通のことになるのも、そう遠くはないはずです。

次回は、最終回です。キー・バリュー型データ・ストアにおいて、特にLow-Level APIでは扱いにくいと言われている、データ・ストアの条件検索について解説します。

有限会社サイバースペース
慶應義塾大学工学部電気科卒。日本IBM、日本HPなどにおいて、製造装置業を中心とした業務系/基幹業務系システムのSE/マーケティングや、3階層C/Sアーキテクチャによる社内業務システム開発などに携わる。現在は、Ajax/Web 2.0関連のセミナー講師/コンサルティング、書籍執筆などを行っている。情報処理学会会員。http://www.at21.net/

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています