Teedaのサンプルアプリを読んでみる(5)

TodoListPageにはinitialize()/prerender()というメソッドが定義されている。Teeda/gettingStartedの「initializeメソッド、prerenderメソッド」には、

  • initialize() : Pageクラスが初期化されるときに呼ばれる。
  • prerender() : 画面が描画されるタイミングで毎回呼ばれる。

とある。初期化処理とかはこの中に書けばいいのか。ただこのサンプルでは戻り値の型がどちらもStringになっているのが気になるところ。doXXX()みたいに遷移先を決められるのか?まぁあとで時間があったら試してみよっと。

ともかくTodoListPageが起動されると最初にprerender()が実行される(initialize()は実質何もしていない)。ここではテーブルTODOのDAO(TodoDao)に対しselectAll()を実行して、TODOの全レコードを取得している。TodoDaoは名前が"Dao"で終わっていて、パッケージ名がサンプルのルートパッケージ(=jp.co.gihyo.javaexpert.todo)+個別パッケージdaoなので、アプリケーション共通のDAOとして扱われるんだろう。個別パッケージwebの下にDAOをおくこともできるけど、SMART deployによると、その場合は

  • pageクラスの固有のDao : 任意のクラス名+"Dao"
  • サブアプリケーション固有のDao : サブアプリケーションの名前+"Dao"

という命名規則に従う必要があるらしい。

で、そのTodoDaoを見ると冒頭jp.co.gihyo.javaexpert.todo.entity.Todoと関連付けられている。

	public Class BEAN = Todo.class;

うーん。static finalがないのね。まぁそれはいいとして、これってTigerアノテーションじゃなくて定数アノテーションだよね。本質的な話じゃないけど、他はTigerアノテーションなんで、ここも合わせればいいのにね。

このTodoクラスだけど、まずパッケージ名のentityは...個別パッケージとしては規定されてないんだね。まぁこれが直接DIされるわけじゃないからいいってことか。あとTABLEアノテーションがないので、クラス名がそのまま関連付けられるテーブル名になる。なのでこのTodoには、テーブルTODOのカラムに対応したプロパティを定義することになるはずなんだけど、ちょっと変。このtodo-manageに入っているdemo.sqlを見るとテーブルTODOは以下の様になっているらしい、

CREATE TABLE TODO(
    ID          NUMERIC(8,0) NOT NULL PRIMARY KEY,
    TODO_TYPE   NUMERIC(2,0) NOT NULL,
    TODO_DETAIL VARCHAR(128) NOT NULL,
    TODO_STATUS NUMERIC(1,0) NOT NULL,
    PRIORITY    NUMERIC(1,0) NOT NULL,
    LIMIT_DATE  DATE
);

ところがクラスTodoのプロパティは

    private Integer id;
    private Integer todoType;
    private String todoDetail;
    private Integer todoStatus;
    private Integer priority;
    private Date limitDate;

なので名前が一致しない。COLUMNアノテーションは使っていないみたいだし、どこで対応付けているんだろう。以前アンダースコア(_)は自動的に削除してマッピングしてくれるような記述を見た記憶があるけど、今探すと見つからない(汗。謎だ...。それとTodoのメソッドsetTypeVal(), setStateVal(), setPriorityVal()は本体が実装されてないけど何か意図があるのか。まぁいいか。TodoDaoに戻る。

そのTodoDaoでいきなり悩むのが次のQUERYアノテーション

	static final String selectMaxId_QUERY = "SELECT max(ID) FROM TODO";

S2Daoリファレンスには

自動的に生成されるSELECT文にWHERE句やORDER BY句を追加するには、QUERYアノテーションを使用します。

となっているけど、どうみてもSELECT文全体が書かれている。SQLアノテーションならこれでいいと思うけど。いろいろ調べてみたら、このSQLアノテーションを導入するときQUERYアノテーションの仕様を「SELECT,DELETE,INSERT,UPDATEではじまっていればSQL文として解釈し、それ以外であればWHERE文以降と解釈する」ように変更したという情報もある。もしそうなっているのであれば、↑でもいいはずだが

  • S2Daoリファレンスには、そういった記述がないみたい
  • そもそもSQLアノテーションにしておけば、私みたいな初心者がソースを読むとき混乱しないと思う。

アノテーションとしてはあと1つ。selectById()の引数を指定するためのARGSアノテーション

    public String selectById_ARGS = "ID";
    public Todo selectById(Integer id);

ここも今ひとつわかりにくい部分。S2Daoリファレンスには「メソッドの引数名は、リフレクションで取得できないためです」とARGSアノテーションの存在理由を記載しているけど、正直ここが???。そのあとに出てくる「検索SQLの自動生成」を読んでからここへ戻ってくるとなんとなくわかる。つまりこのアプリケーションではIDでTODOテーブルを検索する

    SELECT * FROM TODO WHERE ID=999

といったSELECT文を実行したい。そこでSQL文中の"ID"を上記ARGSアノテーションで指定すれば

    SELECT * FROM TODO WHERE ID=?

のようなSQLを自動生成してくれて、?の部分はselectByIdの第1引数をセットして実行してあげますよ、ということなんだろう。1つの引数でカラム名インスタンスのプロパティ名を表していても、リフレクションではその名前がわからないのでARGSアノテーション明示する、ということで最初の説明になるんだろう。

それとARGSアノテーションの説明

引数が1つの場合、ARGSアノテーションは省略できます。

とあるので、省略できると思うけどどうやらNG。これがないと実行されるSELECT文の条件が異なる。こちらはARGSアノテーションがある場合。

SELECT Todo.priority, Todo.id, Todo.todo_type, Todo.todo_status, Todo.limit_date, Todo.todo_detail FROM Todo WHERE Todo.ID = 1

こちらは無い場合。処理は成功するけど、先頭レコードの内容が返されてしまう。

SELECT Todo.priority, Todo.id, Todo.todo_type, Todo.todo_status, Todo.limit_date, Todo.todo_detail FROM Todo

S2Daoリファレンスをよく読むと「検索SQLの自動生成」には

S2Daoは、引数が1つで、ARGSアノテーションが指定されていない場合、引数をDTOとみなし、DTOのプロパティを使って自動的にSQL文を組み立てます。 プロパティ名とカラム名が異なる場合は、COLUMNアノテーションを使ってカラム名を指定します。

と書かれている。つまり引数がDTOじゃない場合、ARGSアノテーションを省略しちゃだめ、ということ。

あと残っているのがselectAll()。名前の通りTODOの全レコードを返すもの。なぜこれだけで全件返すSQLを実行してくれるのか?そのための命名規約があるのか?というと、どうもないらしい。もちろんselectで始まっていなきゃしょうがないけど、ARGSアノテーションとかSQLアノテーションとかがない場合、S2DaoはWHERE句なしのSELECT文を生成するため、結果的に"全件"返される。なのでこのメソッド名をselectZenbuTottekite()などとしても、同じように実行してくれる。

ともかくtodoItemsにはテーブルTODOの全レコードを格納したTodo[]がセットされる。その次のdoDelete()はブラウザ上の[削除]が押されたときの処理。ForEachのItemsSaveでチェックボックスの状態もtodoItemsへセットされているので、あとはそれを見てTodoDao#delete()を実行するかどうかを決めるだけ。だたここで?なのは、上記prerender()が実行されたら、チェックボックスの状態もクリアされたりしない?ということ。でもlogを見るとprerender()はdoDelete()実行後に呼び出されている。なるほど。この辺のシーケンスをきちんと説明する資料はないのかね。

次のdoFinishLogout()は[ログアウト]が押されたときのメソッド。Loginしたときセッションに記録したLogin済みの情報を消去して、Login画面へ遷移させる(LoginPage.classをreturnする)。さらにidが"doFinish"で始まっている場合には、このあとさらにこのPageの情報をクリアして、次の画面へ引き継がれないようにしてくれる。なるほど。

あとはdoDownload()かな。これはまたあとで。