mb_language("uni"); mb_internal_encoding("utf-8"); mb_http_input("auto"); mb_http_output("utf-8"); ?>
前回と今回の記事ではスキーマ変更に伴うアプリケーション側での対応を紹介しています。スキーマ変更要求は「住所録に登録する住所アイテムに対して、新たに電話番号の項目を追加する」というものです。Xprioriに格納するXMLの具体的なスキーマ情報は第10回を参照してください。前回の記事ではそのスキーマ変更に伴う修正の前半として、新規アイテムの追加を行うための修正を紹介しました。修正内容はパラメータの追加や、チェック項目の追加といった機能的な部分での修正がほとんどであり、実際にXprioriと接続する部分での修正はほとんどありませんでした。今回は、既に登録済みでtel要素を持っていないアイテムを編集する部分の修正と、登録したアイテムを閲覧する部分での修正を紹介します。
前回実装した住所アイテム入力では、新規アイテムの入力は問題なく動作するものの、tel要素を持っていない既存アイテムの編集の動作では、編集した内容が反映されないという問題があります。現状の問題を確認してみましょう。
編集対象は、tel要素を持っていないアイテムです。ここでは、IDが000001の「根尾 太郎」を選択し、アイテム内容を編集します。
▲アイテム編集画面
アイテムの電話番号は空なので、「00-0000-0000」と入力し、確認した後で編集を実行します。
▲編集内容確認画面
一見編集が正常に行われたように思えますが、編集実行後に編集したアイテムを見てみると、先ほどの入力画面と変わらず電話番号は空のままで、編集されていないことがわかります。
▲編集実行後の入力画面
この問題はAPIのmodifyXML関数が、編集する要素(この場合はitem要素)と編集後の要素の構造を比較していることが原因です。
今回の場合は、編集前のitem要素はtel要素を持っていないのに対して、編集後のitem要素はtel要素を持っているので、そこで要素のミスマッチが発見され不正な動作とみなされてしまい、下記のようなエラーが出力されます。
<Error> <Name> Node mismatch </Name> <Message> <input>item>tel></input> <Found>ND>address_book>item>name></Found> </Message> <Exception-Number> 1 </Exception-Number> </Error> |
このようなエラーに対応するため、UpdateAddressbookBeanクラスのmodifyItem関数の処理の流れを変更します。前回までのmodifyItem関数では、引数として渡したitemオブジェクトからitem要素のXMLを取得し、Xprioriに格納されている同じIDを持つitem要素に対して、APIのmodifyXML関数をラップしているmodify関数による更新処理を行っていました。
public String modifyItem(ItemBean item) { // ItemBeanのidと同じID属性値を持つitem要素を取得するXPath String modifyXPath = "/ND/address_book/item[@id = " + AddressbookUtils.getXpathConcat(XMLEncoder.xmlEncode(item.getID())) + "]"; // itemから編集用XMLを作り、XPathで指定した既存のアイテムXMLと置き換える String ret = xcon.modify(modifyXPath, item.getItemXML()); return ret; } |
修正後のmodifyItem関数では、modify関数を呼び出す前にtel要素が編集対象のitem要素の子要素に存在するかどうかをチェックします。tel要素がある場合はそのままmodify関数を呼び出しますが、tel要素がない場合は空のtel要素をitem要素の子要素としてaddress要素の下に挿入してから、modify関数を呼び出します。このような流れで更新処理を行うことでmodify関数内部でitem要素の子要素を比較するときに要素のミスマッチが発生しませんので、正常に編集処理が行われます。
public String modifyItem(ItemBean item) { // 編集前にtel要素が存在するかどうかをチェック String countTelNodeXpath = "count(/ND/address_book/item[@id = " + AddressbookUtils.getXpathConcat(XMLEncoder.xmlEncode(item.getID())) + "]/tel)"; //最後のitem要素が存在するかチェックする String telNodeNum_xml = xcon.query(countTelNodeXpath); Document doc = AddressbookUtils.loadXMLFromString(telNodeNum_xml); // tel要素がない場合は空のtel要素を挿入後に編集を実行する if(AddressbookUtils.getQResultInteger(doc) == 0 ){ // 空のtel要素を挿入する xcon.insert("/ND/address_book/item[@id = " + AddressbookUtils.getXpathConcat (XMLEncoder.xmlEncode(item.getID())) + "]/address", "<tel></tel>"); } // ItemBeanのidと同じID属性値を持つitem要素を取得するXPath String modifyXPath = "/ND/address_book/item[@id = " + AddressbookUtils.getXpathConcat (XMLEncoder.xmlEncode(item.getID())) + "]"; // itemから編集用XMLを作り、XPathで指定した既存のアイテムXMLと置き換える String ret = xcon.modify(modifyXPath, item.getItemXML()); return ret; } |
APIのmodifyXML関数内部で編集前と編集後の要素の比較を行っているため、上記のような修正はスキーマ変更のたびに行う必要があります。他の修正内容と比べて少し手間がかかる修正といえるかもしれません。そこで、別の方法として、データベース側での対応法を考えて見ましょう。
これまでは編集処理の対応として、アプリケーション側での対応を紹介しましたが、ここではデータベース側での対応を紹介します。前回の記事では「Xprioriでは現在格納されているデータに対しては何も修正を加えずにアプリケーション側での修正を行うことができます。」と書いていますが、この方法は「アプリケーション側での修正の前に、Xprioriでは現在格納されているデータに修正を加え、アプリケーション側での修正範囲を減らす」という方法なのでデータベース側での対応が必要となります。
具体的には、「アプリケーションでの対応を行う前に、全てのitem要素に対して空のtel要素を挿入する」という対応です。XMS Consoleを利用して、Database AccessのInsert画面から、「XQuery Target」にitem要素の子要素のaddress要素を指定するXPath
/ND/address_book/item/address |
を入力し、「XML to Insert」に空のtel要素
<tel></tel> |
を入力して「InsertXML」を押すことで、address要素の直下に空のtel要素を挿入します。ここではアプリケーション側で個々のitem要素の編集時に行った処理をあらかじめ全てのノードに対して行っています。(tel要素がある状態で、この方法を試すとtel要素を複数持ってしまいます。スキーマ変更前の状態に戻すため、Database AccessのDelete画面で「/ND/address_book/item/tel」を指定しtel要素を削除してください。)この場合、すべてのitem要素の子要素にtel要素が存在するようになりますので、前項で記したmodifyItem関数の修正は不要となります。
今回のような単純なスキーマ変更であれば、この方法で対応するほうが修正コストは少ないですが、複雑なスキーマ変更の場合はアプリケーション側で対応するほうが修正コストが少ないこともあります。スキーマ変更の内容によって使い分けるのがいいでしょう。
最後にアイテムの閲覧部分を実装します。ここで紹介する修正により、アイテム詳細表示画面で登録した電話番号を閲覧することができます。修正するファイルはitemdetail.jspです。アイテム編集時と比べて非常にシンプルな修正となり、アイテムのIDによってXprioriから取得してきたアイテムの電話番号表示部分を追加するだけです。修正箇所を以下に示します。
・・・ <tr><th>電話番号</th><td><%= AddressbookUtils.htmlEncode(item.getTel()) %> </td></tr> ・・・ |
前回修正したItemBeanクラスのコンストラクタ部にさらに修正を加えます。前回は引数として渡されるノードオブジェクトがtel要素を持っているかをチェックした後、tel要素のテキスト値を取得していましたが、これからitem要素にtel要素だけでなく、別の要素が追加されるようなスキーマ変更が行われた場合に、毎回、該当子要素があるかないかのチェックを行う修正は少し手間といえるかもしれません。そこで、汎用関数クラスであるAddressbookUtilsクラスに、ノード名によって指定したノードが持つテキスト値を取得し、もしノードがない場合は空文字を返すgetChildNodeTextByTagName関数を追加します。
public static String getChildNodeTextByTagName(Node parents, String tagName) { //指定したタグ名のノードを取得 Node child = AddressbookUtils.getChildNodeByTagName(parents,tagName); String node_text = ""; if(child != null){ node_text = child.getFirstChild().getNodeValue(); } return node_text; } |
この関数を利用してノードの値を取得することで、ノードがあるかどうかのチェックを行う必要がなくなり、プログラムはすっきりします。
public ItemBean(Node itemnode) { /* * getChildNodeTextByTagNameを利用することで * ノードがあるかないかのif文を削除し、 * スキーマ変更によるコード修正の手間を軽減 */ if(itemnode != null && itemnode.getNodeName().equals("item")){ // IDを取得 this.setID(XMLEncoder.xmlUnencode(itemnode.getAttributes() .getNamedItem("id").getNodeValue())); // 名前を取得 this.setName(XMLEncoder.xmlUnencode(AddressbookUtils .getChildNodeTextByTagName(itemnode,"name"))); // 郵便番号を取得 this.setZip(XMLEncoder.xmlUnencode(AddressbookUtils .getChildNodeTextByTagName(itemnode,"zip"))); // 住所を取得 this.setAddress(XMLEncoder.xmlUnencode(AddressbookUtils .getChildNodeTextByTagName(itemnode,"address"))); // 電話番号を取得 this.setTel(XMLEncoder.xmlUnencode(AddressbookUtils .getChildNodeTextByTagName(itemnode,"tel"))); } } |
今後もスキーマ変更が予測されるアプリケーションの場合は、スキーマ変更時の修正範囲が少なくなるように実装することで、将来予測される修正コストを少しでも少なくすることができます。
今回の記事で住所録アプリケーションは完成となります、まだまだ機能的には物足りない部分もあるかもしれませんが、このアプリケーションの実装の過程を見ることで、Xprioriを利用したアプリケーションの具体的なイメージをつかんでいただけたのではないでしょうか。次回の記事でこのシリーズは最後となります。次回は、これまでの記事を振り返り、Xprioriを利用したアプリケーションを実装してきて気付いた部分を総括したいと思います。
今回作成したjavaクラス・jspファイルはこちらからダウンロードすることができます。
・ jspファイルはWebアプリケーションフォルダのaddress_book/WebContentに配置してください。
・ javaファイルはWebアプリケーションフォルダのaddress_book/WebContent/WEB-INF/srcに配置してください。
また、ご自分でクラスを新規作成する場合はEclipseのメニューバーより、[ファイル]→[新規]→[クラス]を選択し、[次へ]を押してください。
次の画面で[ソースフォルダ]に「address_book/src」、[パッケージ]に「addressbook」を入力し、[名前]に適切なクラス名を入力し、[終了]を押すことで、新規のクラスを作成することができます。
▲このページのTOPへ