登録-確認-完了でデータを登録するサンプル

よくある(?)データ登録機能をStruts2で作ってみた。登録画面で情報を入力して、確認画面で入力内容を確認して、登録処理を実行するという流れ。画面間の情報の受け渡しはセッションでなくリクエストで行く(hiddenで渡す)。セッションを使うことももちろん可能なんだけど、自由度が下がっちゃうからあえてリクエストでいく。タグブラウザなんかで同一の機能を2つ開いて操作をしちゃうとセッションだと操作が(データが)干渉しちゃう。ユーザに「同じ機能を2枚以上開いちゃだめ!」といえれば問題ないけどねー。

以下は、画面遷移と対応するボタンやResultTypeを示した図。登録画面と確認画面の遷移部分だけchainを使って、リクエストレベルで情報を受け渡している。

各画面は次のような設計ルールで実現する。(なるたけシンプルに!)

  • 1画面1JSP1アクションクラス
  • JSPの名前(.jsp部分を除く)とアクションクラスの名前をあわせる
  • JSPディレクトリ階層とアクションクラスのパッケージ階層をあわせる

ポストバックな設計にするので、画面上のボタンに対応する処理は対応するアクションクラスのメソッドが実装する。たとえば、登録画面用の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" %>

とかなどを利用できるようになる。http://struts.apache.org/2.0.9/docs/struts-tags.html

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>

タグとタグでURL文字列を作っている。タグの%{url}で先ほど生成したURL文字列を参照している。
こいつらも、Generic TagsのData Tagsという種類。

Login.jsp

今度はLogin.jspから。

<s:form action="Login">
    <s:textfield key="username"/>
    <s:password key="password" />
    <s:submit/>
</s:form>

タグでformを定義している。サブミットしたときの遷移先(アクション)は、Loginアクションを指定している。
タグはテキストフィールドを、タグはパスワードフィールドを意味する。属性keyに定義してあるのは、Loginクラスのプロパティ名ということ。直感的だなー。

設定ファイル

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>

タグの部分はstruts2が持っているプロパティを設定している部分。struts.enable.DynamicMethodInvocationの部分は、リクエストに、アクションクラス!メソッド.action、という形式でアクションを呼び出せないようにする、ってことらしい。ほぉほう。つぎに、struts.devModeの部分は、開発者モードじゃないよってことみたい。
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をインクルードする。Struts1のときはweb.xml側で分割したstruts-config.xmlを読み込むような設定をしていたから、楽になった感じがする。

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に書くことが多かった気がする。

ふぅ。

アーロンチェアが届く!

ボーナスを形あるものに変える行動の第一弾。まだ良さを実感できないけど、机に向かうことが多くなるとわかるかも。「ポリッシュドアルミニウムベース ポスチャーフィットフル装備 Bサイズ クラシック」ってやつ。
「1日30分」を続けなさい!人生勝利の勉強法55
古市幸雄さんの『「1日30分」を続けなさい!人生勝利の勉強方55』に載ってた、この一文を素直に信じて買っちゃった。「予算が許すのであればハーマンミラーの「アーロンチェアー」が最高です。・・・長期間勉強するのであれば、真っ先に椅子に投資することを強くおすすめします。」得供されやすいなー。

1000M位泳いで適度に疲れた

久々に有明スポーツセンターに行った。脱衣所のロッカーが新しくなっていた。また、水着の脱水機なるものがおいてあった。帰りに本がぬれることも少なくなるかも!
都バスの有明1丁目から歩いたんだが、横断歩道を3つ渡ることになるので思ったより時間がかかる。感覚的にはお台場海浜公園よりも近いかなー。