Scala事始めにLiftを触ってみる...当然Mavenも (3)
今回は、データベースアクセスの練習をします。
- 制限事項:Rails分かりません...
- Liftにおける役割分担
- Jettyの起動とOutOfMemory回避
- データベースからデータを取得して一覧ページを作成
制限事項:Rails分かりません...
今や、Webアプリフレームワークと言えばRailsなしでは語れないことは重々承知していますが、わたくしRailsほとんどやったことありません(インストールだけ)ので、比較してあれこれ言うのはできません。ごめんなさい。
(というか、有名どころのフレームワークは説明できるほど知っているものがありません...)
Liftにおける役割分担
"archetype:generate"で生成されるファイルの役割分担について。まだLiftの仕様やその変遷についてほとんど知らないので、バージョン2.4-M4をちょっと触ってみた素人の目線で。
- HTML
- デザイン。カスタムタグも使える?けど、カスタムタグ無しで代わりに既存のタグの属性にclass="lift:(function-class.function-name)"(下記例参照)とすることでテンプレートにできる。(yuroyoroさんのサイトにあるlift:snippetカスタムタグが使えないのはバージョンの違いなのか、それとも設定デフォルト値の違いなのか、今のところ不明。)
- (Scala)Boot.scala
- 初期設定。メニューの設定なども行える。
- (Scala)snippet
- 最終的にXHTMLとして出力する文字列を返す関数の定義と、任意の関数の定義。
- (Scala)snippet以外(Testは除く)
- モデル、クラスライブラリなど?が含まれる。
- (Scala)Testコード
- BDDスタイルのテストケースが作成される。
テンプレートの例:
<div class="lift:HelloWorld.howdy">[HelloWorld.howdy]</div>
※カギカッコはHTMLファイルを直接ブラウザやWYSIWYGエディタで見たときに、値が出力される位置を確認するために書いています。
src/main/webapp/index.htmlを練習に使うとしたら、最低限index.htmlとsnippet/*.scalaだけ編集すれば動かせます。
今回はこれをベースに実験します。
Jettyの起動とOutOfMemory回避
実際に稼動させる環境は任意のサーブレットコンテナとなりますが、ローカルテスト環境はMavenで完結させるためJettyを使います。
起動するには、以下のコマンドを実行します。コマンドウィンドウは占有されるので、Jetty用に1つ開いておきましょう。
> mvn jetty:run -U
※"-U"オプションは「依存ライブラリの更新を強制的にチェックするようになる」(参考サイト参照)だそうです。
JettyをMavenのデフォルト設定で使用した場合、何度かホットデプロイを繰り返すとOutOfMemoryが発生してしまいます。Ctrl+Cで停止できない場合は、タスクマネージャから停止しましょう。
これを回避するには、環境変数MAVEN_OPTSもしくは直接mvn.batに、Java起動オプションを設定します。なお、このオプションは今後Mavenで起動するJavaプロセスすべてに影響します。
設定の例:
set MAVEN_OPTS=-XX:MaxPermSize=512M -Xmx512M
データベースからデータを取得して一覧ページを作成
前回作成したnoteというプロジェクトで続けます。
まずは、snippetを作ります。HelloWorld.scalaと同じディレクトリにSelectDB.scalaを作成します。内容は、
package net.argius.snippet class SearchDB { def users: scala.xml.Elem = <strong>users</strong> }
とします。
次に、index.htmlの内容をいったん消して、
<div id="main" class="lift:surround?with=default;at=content"> <span class="lift:SearchDB.users">[LIST]</span> </div>
に書き換えます。
この状態で、コンパイルを実行し完了してからJettyがリロードするのを待って、ページを再表示してみます。
上手く表示されたら、テンプレートからsnippetを呼び出すことに成功したことになります。
※scalaファイルはJetty起動時にコンパイルされますが、scalaファイルを変更したあとでホットデプロイする場合は、手動でコンパイルをしてください。
続いて、データベースからSQLを使って値を取得し、一覧として表示させます。
SearchDB.scalaを
package net.argius.snippet import _root_.net.liftweb.db._ import _root_.net.liftweb.util._ import Helpers._ class SearchDB { def users: CssSel = { val result: (List[String], List[List[String]]) = DB.runQuery("SELECT FIRSTNAME, LASTNAME FROM USERS") val headers: List[String] = result._1 val values: List[List[String]] = result._2 "#header *" #> headers & "#value *" #> values.map(xa => "td *" #> xa) } }
に書き換え、index.htmlを
<div id="main" class="lift:surround?with=default;at=content"> <table class="lift:SelectDB.users"> <thead> <th id="header">[header]</th> </thead> <tbody> <tr id="value"><td>[value]</td></tr> </tbody> </table> </div>
に書き換えます。
デフォルトでは、組み込みDBのH2が適用され、プロジェクトの直下にDBファイルが作成されています。このusersテーブルの内容が表示されます...が、usersテーブルは最初は空です。
何かしらの方法でDBにレコードをメニューの"Sign Up"でユーザを追加すれば、一覧に反映されるはずです。
今度は、他のDBMSに切り替えてみます。ここではPostgreSQL8.3を使います。DBは別途用意してください。"public.users"というテーブルが存在しない場合は自動的に作成されます。
pom.xmlをEclipseのPOMエディタで開き、"Dependencies"タブを選択すると、以下の画像のようなページが開きます。
"Add"ボタンを押すとDependencies検索ダイアログが開くので、ダイアログの上側の入力欄に"postgre"と入力するといくつか候補が表示されます。8.3-603.jdbc4を選択すると赤枠内のところに情報が表示されるので、残りを入力してファイルを保存すれば、PostgreSQLが登録されます。
pom.xmlには
<dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>8.3-603.jdbc4</version> <type>jar</type> <scope>runtime</scope> </dependency>
のように反映されます。
そして、src/main/resources/props/default.propsに
# Properties in this file will be read when running in dev mode db.driver = org.postgresql.Driver db.url = jdbc:postgresql://localhost:5432:defaultdb db.user = postgres db.password = postgres
と入力し、Jettyを再起動します。
DBの設定は、Boot.scalaの"DB.defineConnectionManager"辺りを
val vendorPG = new StandardDBVendor("org.postgresql.Driver", "jdbc:postgresql://localhost:5432:defaultdb", Full("postgres"), Full("postgres")) DB.defineConnectionManager(DefaultConnectionIdentifier, vendorPG)
とすることもできます。これを使って、デフォルト以外の接続先を設定することもできそうです。(今回はやりません。)
最後に、modelを使った検索。Userというmodelがデフォルトで生成されているので、これを使ってSQLの場合と同じように表示させてみましょう。
SearchDB.scalaを
package net.argius.snippet import _root_.net.liftweb.mapper._ import _root_.net.liftweb.util._ import net.argius.model._ import Helpers._ class SearchDB { def users: CssSel = { val records: List[User] = User.findAll(OrderBy(User.id, Ascending, NullsLast)) val headers: List[String] = List("First Name", "Last Name") val values: List[List[String]] = records.map(r => List(r.firstName, r.lastName).map(x => x.toString)) "#header *" #> headers & "#value *" #> values.map(xa => "td *" #> xa) } }
に書き換えます。
以下の画像は、データを追加して一覧表示したところです。
SQLの場合もmodelの場合も、リストの操作がしやすいのが良いですね。Javaではこうはいきませんからね。
今回はここまでです。
Liftの流儀として洗練されているかどうかは判断できませんが、Liftのスタートアップとしてはこんなところでしょうか。