登録-確認-完了でデータを登録するサンプル
よくある(?)データ登録機能をStruts2で作ってみた。登録画面で情報を入力して、確認画面で入力内容を確認して、登録処理を実行するという流れ。画面間の情報の受け渡しはセッションでなくリクエストで行く(hiddenで渡す)。セッションを使うことももちろん可能なんだけど、自由度が下がっちゃうからあえてリクエストでいく。タグブラウザなんかで同一の機能を2つ開いて操作をしちゃうとセッションだと操作が(データが)干渉しちゃう。ユーザに「同じ機能を2枚以上開いちゃだめ!」といえれば問題ないけどねー。
以下は、画面遷移と対応するボタンやResultTypeを示した図。登録画面と確認画面の遷移部分だけchainを使って、リクエストレベルで情報を受け渡している。
各画面は次のような設計ルールで実現する。(なるたけシンプルに!)
ポストバックな設計にするので、画面上のボタンに対応する処理は対応するアクションクラスのメソッドが実装する。たとえば、登録画面用のSampleRegistrationPageクラスは次のメソッドを定義している。
- input ... 登録画面表示用
- next ... 次画面遷移用
- cancel ... キャンセル用
あと、アクションを実行するためのURIは次のような設計にする。
- アクションクラス/メソッド.do
こんな感じの決まりごとで作ったアーカイブはここに置いてある。(Eclipse3.3のWTPで作成)
登録-確認-完了のサンプル
・・・
今回やってみた確認画面を経由するような処理はセッションを使うことのほうが多いのかもしれない。会社でも使ってるし。リクエストだとhiddenを使うから記述が面倒。それに比べてセッションだと楽チン。これが理由かな。ただ、個人的には、Struts2のhiddenタグで面倒くささを感じなかったので、リクエストのほうがいいかもなぁ、と感じている。(Struts1のとき、セッション上のオブジェクトのリセット制御が面倒くさかったことがあったなー。)
Zero Configurationなるものに惹かれたんだけど、ポストバックなアクションは実現できず・・・
Struts2を使って、ある画面に存在するリンクやボタンに対応する処理を、一つのアクションにまとめたかった。いわゆるポストバック的な考え方に基づいた実装をしたかった。なんだけど、設定ファイルなしでは、実現できそうにない。。。少し残念。。。まぁしゃーないのか。
以下、備忘録。
Zero Configurationというのは、struts.xmlにごりごりと定義を書くんじゃなくて、規約+アノテーションでアクションと遷移先なんかを対応付けるやり方。http://struts.apache.org/2.x/docs/zero-configuration.html
web.xmlのFiterDispatcherの設定でinit-paramにactionPackagesを定義することから始まる。
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class> <init-param> <param-name>actionPackages</param-name> <param-value>com.foo</param-value> </init-param> </filter>
このように書くと、com.fooパッケージ以下のアクションクラスをアクションとして認識してくれる。struts.xmlに書かなくても!おお、すばらしい!!!
だが喜んではいられなかった。上記のやり方で認識させたアクションクラスはアクションメソッドを一つしかかけないみたい。正確には@Resultsアノテーションがクラスレベルでしか記述できないらしい。
Results are defined with the Result and Results annotations at the class level.
えー、あかんやん。ポストバック的にアクションを使いたいので、画面に対応するアクションクラスに複数のアクションメソッドを定義したいのに。画面を初期化して表示するinputアクション、次の画面に進むnextアクションとか。
外部結合、いつもどっちだったかわからなくなるなぁ
今日はリレーショナルデータベースの論理設計の研修。外部結合というキーワードが出てきて、「ああ、あれね」と思ったんだけども、実際にどうやるんだっけ、とむずむずしてきた。外部結合をするときのSQL文を書くとき、どっちが補完されるのか、ということ。(SQL文自体をあんまり書かないからだな。)メモしとこう。
LEFT JOINを使って外部結合する場合は、全ての行を表示する側のテーブル(優先する側のテーブル)を、左側に記述する。ほう、なんだ、簡単じゃないか。
商品テーブルと受注テーブルで考える。注文されていない商品がある場合、受注テーブルに出てこない商品があるということ。そこで、全ての商品に対する受注を検索する場合のFROM句はこんな感じになる。
FROM 商品テーブル LEFT JOIN 受注テーブル ON 商品テーブル.商品ID = 受注テーブル.商品ID
LEFT JOINがすっきりしないなぁと思っていたのは、ORACLEで育ったからかもしれない。ORACLEだと補完される側のテーブルに(+)をつければいいのですっきり。この(+)をつける側を意識しているから、LEFT JOINがすっきりとしていないっぽい。
このページわかりやすいなー。http://www.pursue.ne.jp/jouhousyo/SQLDoc/select22.html
またマウスが壊れたよ…
1ヶ月前に購入したLogicoolのMX620が壊れた。まったく動かなくなるという症状。前に使ってたMX610は最悪(クリックするとダブルクリックになっちゃう)だったけど、Logicoolのマウスがいいからなぁ、と思って購入しただけにちょっと残念。まぁ、保証書とっておいたので量販店に持っていって交換してもらえたけど。
「もしかして他の人もMX620が壊れちゃったりしてるのか!?」と思ってググってみたが見つからず。購入したマウスがたまたま問題ありだったってことかな。まぁそういうことだ。
Struts2のblank.warを読んでみた
Struts2がデファクトになるのかなぁ。どうだろう。わかんないけどかなり知っておいて損はないだろう。ということでStruts2の勉強開始。struts-2.0.9を対象に。
アクション
Struts2でのアクションクラスはActionSupportクラスを継承するようだ。
サンプルには以下のクラスが定義されている。
- ExampleSupportクラス ... サンプル用のベースクラス
- HelloWorldクラス ... メッセージを表示するアクションクラス
- Loginクラス ... Login処理っぽいことを行うアクションクラス
ExampleSupportクラスはActionSupportクラスを継承しているだけで中身は空っぽ。
ActionSupportクラスはActionクラスをサポートしてくれるもの(そのままやな)。"success"をリターンするexecute()メソッドや、"input"をリターンするinput()メソッドなどを提供する。http://struts.apache.org/2.0.9/struts2-core/apidocs/com/opensymphony/xwork2/ActionSupport.html#input()
HelloWorldクラス
アクションを定義している部分はこれ。
package example; /** * <code>Set welcome message.</code> */ public class HelloWorld extends ExampleSupport { public String execute() throws Exception { setMessage(getText(MESSAGE)); return SUCCESS; } ...
execute()メソッドがアクションとして実行されるメソッド。Stringをリターンすればいいみたい。シンプル!!!ここでは、"success"という文字列をリターンしている。
getText(arg)というメソッドは、ActionSupportクラスが提供するもので、メッセージリソースからメッセージを取得するもの。
jsp
struts2のタグリブはこんな感じで定義する。
<%@ taglib prefix="s" uri="/struts-tags" %>
HelloWorld.jsp
HelloWorld.jspからStruts2のタグライブラリの使い方を見てみる。
<title><s:text name="HelloWorld.message"/></title>
リソースファイルからnameで指定したキーに対応するプロパティに置き換えるタグ。
Generic TagsのData Tagsという種類。
<h2><s:property value="message"/></h2>
スタックからmessegeというプロパティの値に置き換えるタグ。おそらくスタック上にHello
Worldオブジェクトが乗っかっていれば、HelloWorldオブジェクトに対してgetMessage()が呼ばれているんだと思われる。
こいつも、Generic TagsのData Tagsという種類。
<s:url id="url" action="HelloWorld"> <s:param name="request_locale">en</s:param> </s:url> <s:a href="%{url}">English</s:a>
こいつらも、Generic TagsのData Tagsという種類。
設定ファイル
web.xml
一部抜粋するとこんな感じ。
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
FilterDispatcherなるFilterがActionを実行する。すべてのリクエストをマッピングしないといけないようだ。http://struts.apache.org/2.0.9/struts2-core/apidocs/org/apache/struts2/dispatcher/FilterDispatcher.html
struts.xml
Struts1のときはstruts-config.xmlだったのだがstruts.xmlになった。あと、この設定ファイルの置き場所が、クラスパス上になった。サンプルでは、classes直下においてある。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constant name="struts.enable.DynamicMethodInvocation" value="false" /> <constant name="struts.devMode" value="false" /> <include file="example.xml"/> <!-- Add packages here --> </struts>
struts-core-2.0.9.jarにorg/apache/struts2/default.propertiesなるファイルがあり、様々なプロパティのデフォルト値が定義してある。struts.devModeについてはこんな感じ。
### when set to true, Struts will act much more friendly for developers. This
### includes:
### - struts.i18n.reload = true
### - struts.configuration.xml.reload = true
### - raising various debug or ignorable problems to errors
### For example: normally a request to foo.action?someUnknownField=true should
### be ignored (given that any value can come from the web and it
### should not be trusted). However, during development, it may be
### useful to know when these errors are happening and be told of
### them right away.
struts.devMode = false
ふーん。
で最後に、
example.xml
struts.xmlがインクルードする定義ファイル。アクションのマッピングが定義してある。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="example" namespace="/example" extends="struts-default"> <action name="HelloWorld" class="example.HelloWorld"> <result>/example/HelloWorld.jsp</result> </action> <action name="Login_*" method="{1}" class="example.Login"> <result name="input">/example/Login.jsp</result> <result type="redirect-action">Menu</result> </action> <action name="*" class="example.ExampleSupport"> <result>/example/{1}.jsp</result> </action> <!-- Add actions here --> </package> </struts>
この定義の意味するところはこういうこと。
- /example/HelloWorld.actionというリクエストに対してexample.HelloWorldクラスを対応付けている。execute()メソッドを実行した後に、/example/HelloWorld.jspに遷移する。
- /example/Login_メソッドというリクエストに対してexample.Loginクラスのメソッドを対応付けている。呼び出したメソッドがinputという文字列を返した場合は、/example/Login.jspに遷移する。inputという文字列でない場合は、/example/Menu.actionにリダイレクトする。
- /example/JSPファイル名.actionというリクエストに対してexample.ExampleSupportクラスを対応付けている。execute()メソッドを実行後、/example/JSPファイル名.jspに遷移する。
あと、この定義には明示されていないが、/example/Login.actionというリクエストに対して、example.Login#execute()が対応付けられている。#デフォルトのルールだろうか?
そのほかのファイル
Login-validation.xmlなるファイルを定義することで、Loginアクションのバリデーションを定義できるっぽい。
<validators> <field name="username"> <field-validator type="requiredstring"> <message key="requiredstring"/> </field-validator> </field> <field name="password"> <field-validator type="requiredstring"> <message key="requiredstring"/> </field-validator> </field> </validators>
どこにもこのファイルを参照する定義は書いてないなぁ。アクションクラス名-validation.xmlとして、クラスパス上に配置するってことがルールなのかな。
定義ファイルは参照しやすくなっているみたいだけど、実際にバリデーション定義がやりやすいんだろうか?Struts1のバリデータプラグインとかではメンテがつらかったからなぁ。#テストのことを考えて、結局、ActionFormに書くことが多かった気がする。
ふぅ。