argius note

プログラミング関連

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.xmlEclipseの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のスタートアップとしてはこんなところでしょうか。