CocoonによるWebサイト構築テクニック
2005年02月01日作成CocoonによるWebサイト構築テクニック 第9回 データベースへの更新処理
入力フォームの作成
今回のシナリオでは、前回も使ったHSQLDBのサンプルテーブルにデータの追加を行う。前回の記事でも扱ったが、サンプルテーブルの内容を表 1と表 2に再度示しておこう。
ここでは、フォームを使ってクライアントマシンのWebブラウザから従業員テーブルに追加するデータを追加する。作成するページイメージを図 1に示す。表示イメージと2つのテーブルを見るとわかるとおり、「部署」コンボボックスには「DEPARTMENT」テーブルの「NAME」列の内容(部署名)が示され、「追加」ボタンをクリックしたときに、「ID」列の内容(部署のID)が送信されるようにしたい。これを実現するためのXSPファイルとXSLTスタイルシートをそれぞれリスト 1とリスト 2に示す(サイトマップファイルは他の設定とともにリスト 7に示す)。内容としては前回の復習となるので、各自でリストを確認していただきたい。
表 1 サンプルテーブル「EMPLOYEE」
表 2 サンプルテーブル「DEPARTMENT」
図 1 従業員追加フォーム
<?xml version="1.0"?>
<xsp:page language="java"
xmlns:xsp="http://apache.org/xsp"
xmlns:esql="http://apache.org/cocoon/SQL/v2">
<DepartmentList>
<esql:connection>
<esql:pool>personnel</esql:pool>
<esql:execute-query>
<esql:query>SELECT * FROM DEPARTMENT</esql:query>
<esql:results>
<esql:row-results>
<department>
<esql:get-columns/>
</department>
</esql:row-results>
</esql:results>
</esql:execute-query>
</esql:connection>
</DepartmentList>
</xsp:page>
?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<title>ESQLロジックシートサンプル</title>
</head>
<body>
<h1>従業員の追加</h1>
<form action="InsertEmployee.xsp" method="post">
<table>
<tr><th>従業員番号</th><td><input type="text" id="ID" name="ID" /></td></tr>
<tr><th>名前</th><td><input type="text" id="NAME" name="NAME" /></td></tr>
<tr><th>部署</th><td><xsl:apply-templates/></td>
<td><input type="submit" value="追加" ID="submit" NAME="submit" /></td>
</tr>
</table>
</form>
</body>
</html>
</xsl:template>
<xsl:template match="DepartmentList">
<select id="DEPARTMENT" name="DEPARTMENT">
<xsl:apply-templates/>
</select>
</xsl:template>
<xsl:template match="department">
<option value="{ID}"><xsl:value-of select="NAME"/></option>
</xsl:template>
</xsl:stylesheet>
<map:serializer logger="sitemap.serializer.html" mime-type="text/html" name="html" pool-grow="4" pool-max="32" pool-min="4" src="org.apache.cocoon.serialization.HTMLSerializer">
<doctype-public>-//W3C//DTD HTML 4.01 Transitional//EN</doctype-public>
<doctype-system>http://www.w3.org/TR/html4/loose.dtd</doctype-system>
<encoding>UTF-8</encoding>
</map:serializer>
エンコーディングの設定
さて、クライアントからデータを送信するときに問題になるのが文字のエンコーディングだ。XSPファイルがサーブレットエンジンから受け取る文字列は、デフォルトではISO-8859-1エンコーディングになっている1。このエンコーディングでは日本語は扱えないため、そのままでは日本語の文字を受信したときに文字化けしてしまう。一般には、Webブラウザはフォームに使われているのと同じエンコーディングでデータを送信する。したがって、Cocoon側ではフォームを送信するときのエンコーディングを使ってフォームから受け取ったデータを読むようにすれば、文字化けせずに正しく文字列を受け取れるわけだ。
CocoonからクライアントへWebページが送られるときには、デフォルトでISO-8859-1エンコーディングが使われている。では、どうして日本語の文字が読めているのかというと、それらの文字は以下のように、すべて文字参照の形で送られている。
これを見ると、日本語の文字は「&#」と「;」で囲まれたUnicodeの文字コードで示されていることがわかる。このデフォルトの状態のフォームからサーバへデータを送ると日本語の文字は文字参照の形で送られる。たとえばフォームに「柳生みゆ」と入力すると「柳生みゆ」という文字列が送られる2。このままデータベースに登録するわけにはいかない。もちろん文字参照を文字に戻すことは可能だが、そのような手間をかけるよりも最初から日本語が扱えるエンコーディングで送受信したほうがよい。
Cocoonからブラウザに送信する文字列のエンコーディングは、サイトマップファイルの指定で決定される。Cocoonディレクトリにあるサイトマップファイル(sitemap.xmap)3をエディタで開いて、「sitemap.serializer.html」を見つけていただきたい。そこにHTML Serializerに関する設定が記述されているので、encoding要素を追加してCocoonからブラウザへの送信に使うエンコーディングを指定する。リスト 3の①のように記述すれば、Cocoonからブラウザに送られる文字はUTF-8を使って送信され、同時に、ブラウザが送信するフォームデータもUTF-8で送られるようになる。
Requestロジックシート
次に、フォームから送信されたデータを受け取るXSPファイルを作成しよう。記述例をリスト 4に示す。フォームデータを受け取るには、Requestロジックシートを利用することができる。同ロジックシートを使ってフォームデータを受け取るには、名前空間xsp-requestを宣言し(リスト 4-①)、xsp-request:get-parameter要素を使用する(リスト 4-②)。xsp-request:get-parameter要素のname属性にパラメータ名を指定すると、同要素が、指定された名前を持つパラメータ値に置き換わる。フォームからGET、POSTどちらで送られたデータでもxsp-request:get-parameter要素で取り出すことができる。
本サンプルでは、IDパラメータとDEPARTMENTパラメータは数値として扱うのでこのままでかまわないが、NAMEパラメータには日本語のデータが入れられるのでエンコーディングを考慮する必要がある。エンコーディングはform-encoding属性で指定する。サイトマップファイルの指定でUTF-8を使ってフォームを送信したので、受け取るデータもUTF-8になっているはずだ。「form-encoding="UTF-8"」と書けば正しいエンコーディングで文字列が受け取れる。
<?xml version="1.0"?>
<xsp:page language="java"
xmlns:xsp="http://apache.org/xsp"
xmlns:xsp-request="http://apache.org/xsp/request/2.0"
xmlns:esql="http://apache.org/cocoon/SQL/v2">
<result>
<xsp:logic>
String id = <xsp-request:get-parameter name="ID"/>;
String department = <xsp-request:get-parameter name="DEPARTMENT" />;
String name = <xsp-request:get-parameter name="NAME" form-encoding="UTF-8"/>;
</xsp:logic>
<esql:connection>
<esql:pool>personnel</esql:pool>
<esql:execute-query>
<esql:query>
INSERT INTO EMPLOYEE VALUES(
<esql:parameter><xsp:expr>id</xsp:expr></esql:parameter>,
<esql:parameter><xsp:expr>department</xsp:expr></esql:parameter>,
<esql:parameter><xsp:expr>name</xsp:expr></esql:parameter>
)
</esql:query>
<esql:update-results><success/></esql:update-results>
<esql:error-results><error><esql:get-message/></error></esql:error-results>
<esql:no-results><no-results/></esql:no-results>
</esql:execute-query>
</esql:connection>
</result>
</xsp:page>
<esql:query>
INSERT INTO EMPLOYEE VALUES(
<xsp:expr>id</xsp:expr>,
<xsp:expr>department</xsp:expr>,
'<xsp:expr>name</xsp:expr>'
)
</esql:query>
SQLステートメントにパラメータを渡す
ESQLロジックシートの基本的な使い方は、SELECTステートメントを使うときと同じだ。ここで新たに考慮することは、SQLステートメントにパラメータを渡す方法と、更新処理が成功したかどうかを知る方法だ。
SQLステートメントにパラメータを渡したい場合、いちばん簡単なのは文字列としてSQLステートメントを作成して、esql:queryの内容に記述することだ。パラメータはxsp:expr要素で参照すればよい。たとえば、リスト 5のようになる(nameは文字列データなので、xsp:exprの前後に引用符が必要であることに注意)。しかし、この方法には欠点がある。もし、nameパラメータの中に引用符が含まれていたらどうなるだろうか? 多くの場合エラーが発生してSQLの実行が行われないだろう。さらには、パラメータ中にSQLステートメントとして意味のある文字列が悪意をもって構成されていたら、意図に反した処理が実行されてしまうおそれもある。もし、リスト 5の方法で文字列を渡すのなら、その中に不正な文字列が含まれていないかチェックしてから使わなければならない。
ESQLロジックシートで用意された要素esql:parameterを使うと、このような問題を解決することができる(リスト 4-③)。使い方は、xsp:exprのまわりをesql:parameterの開始タグと終了タグで囲むだけだ(nameは文字列データだが、xsp:exprの前後に引用符は不要)。これだけで、たとえパラメータ文字列中にSQLステートメントとして意味のある文字列が含まれていたとしても、それは必ず文字データとして扱われる。ステートメントの区切り文字やキーワードとして扱われることはない。
それに加えて、esql:parameterを使うとSQLの実行にjava.sql.PreparedStatementが使われるようになる。PreparedStatementとはプリコンパイルされたSQLステートメントを表すオブジェクトであり、ステートメントを複数回効率的に実行する目的で使用できる。つまり、2回目以降の実行で速度が改善されることが期待できるのである。とはいえ、esql:parameterを使った場合、エラーメッセージがわかりにくくなる可能性がある。実行される頻度が低く、かつパラメータ値のチェックが比較的簡単ならば、esql:parameterを使わずにxsp:exprだけを使う方法も考慮できるだろう。
更新処理の結果を取得する
次に、リスト 4の④を見ていただきたい。esql:query要素のあとに、esql:update-results要素、esql:error-results要素、およびesql:no-results要素を記述した。これらの要素は、SQLステートメントを実行した結果に応じて内容が出力される(表 3)。リスト 4の例で言えば、更新処理が成功するとesql:update-results要素の内容である「
まとめ
最後にこの結果をブラウザに送るためのXSLTスタイルシートをリスト 6に、サイトマップファイルをリスト 7に示す。また、フォームデータ送信後の結果表示を図 2に、図 2から「従業員リストに戻る」をクリックした後の表示を図 3に示す(XSPファイルやXSLTスタイルシートは前号のリストを参照)。フォームとデータベースを組み合わせた処理の参考にしていただきたい。
表 3 更新結果を示すESQL要素
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<title>ESQLロジックシートサンプル</title>
<style>
th {text-align:center}
td {text-align:center}
</style>
</head>
<body>
<h1>従業員追加処理</h1>
<xsl:apply-templates/>
<hr />
<p><a href="EmployeeList.html">従業員リストに戻る</a></p>
<p><a href="AddEmployeeForm.html">従業員追加フォームに戻る</a></p>
</body>
</html>
</xsl:template>
<xsl:template match="success">
<p>従業員追加処理に成功しました。</p>
</xsl:template>
<xsl:template match="error">
<p>従業員追加処理中にエラーが発生しました。<br />
<xsl:value-of select="."/></p>
</xsl:template>
</xsl:stylesheet>
<?xml version="1.0" encoding="UTF-8"?>
<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
<map:pipelines>
<map:pipeline>
<map:match pattern="EmployeeList.html">
<map:generate type="serverpages" src="EmployeeList.xsp"/>
<map:transform src="EmployeeList.xslt"/>
<map:serialize/>
</map:match>
<map:match pattern="AddEmployeeForm.html">
<map:generate type="serverpages" src="DepartmentList.xsp"/>
<map:transform src="AddEmployeeForm.xslt"/>
<map:serialize/>
</map:match>
<map:match pattern="InsertEmployee.xsp">
<map:generate type="serverpages" src="InsertEmployee.xsp"/>
<map:transform src="InsertResult.xslt"/>
<map:serialize/>
</map:match>
</map:pipeline>
</map:pipelines>
</map:sitemap>
図 2 追加処理の結果表示
図 3 追加処理後の従業員リスト
1 ISO-8859-1は8ビットの文字コード体系であり、英語、ドイツ語、フランス語など、西ヨーロッパの言語で使うアルファベットや記号がカバーされている。
2 Internet Explorer 6.0 SP2およびNetscape 7.1で動作確認を行った。古いブラウザでは、桁数の多い文字参照が扱えないものもある。
3 ここで記述するのは今回のサンプルのために作るサイトマップファイル(リスト 7)ではなく、Cocoonに添付されているサイトマップファイルだ。