torutkのブログ

ソフトウェア・エンジニアのブログ

「Grailsをマスターする:Grailsとレガシー・データベース」を読みながら動かしてみる

Grails をマスターする: Grails とレガシー・データベースの記事に沿ってGrailsの習得を進めます。

id:torutk:20090725の続きです。

一つ記事を飛ばしました

Grailsをマスターするシリーズは、前回読んだGrails をマスターする: Grails サービスと Google Mapsと今回との間に、Grails をマスターする: Grails とモバイル Webという記事があります。その記事では、携帯電話端末で表示するためのWMLiPhone用HTMLといった小型携帯端末向けの表示を作成する内容について書かれています。記事では携帯電話のエミュレータソフトウェアを使った確認もありますが、動かしてみることはパスしました。

記事の最初はGroovyのDB読み書きスクリプト記述から

リレーショナルデータベース上のテーブルに格納されたデータをXMLファイルに保存すること、保存したファイルからリレーショナルデータベースのテーブルに格納すること、の2つの処理をGrailsではなく、Groovyのスクリプトで実現する内容です。

データベース管理ソフトウェアなら、大抵はファイルとのやり取りが可能なので、わざわざスクリプトを書く必要はないかもしれません。が、その方法はデータベース製品固有の方法なので、異なるデータベース製品間でデータを移行したり、といったときには有用かもしれません。

なんかリストアするスクリプトが・・・

最初に、今まで作ってきたドメイン・クラスAirportのデータを、リレーショナルデータベース上のテーブルからXML形式でファイルに落とすGroovyのスクリプトを紹介しています。続いて、XML形式のファイルからリレーショナルデータベース上のテーブルにデータを復元するGroovyのスクリプトがあるはずですが、なぜかAirportとはぜんぜん違う内容を違うテーブルに入れるスクリプトとなっています。

AirportをXMLファイルから取得しテーブルに入れるスクリプトを作ってみる

ということで、記事のサンプルスクリプトを参考にAirportからデータを取るコードを作成します。

println "restore Airport Database data from XML file"
if (args.size()) {
  f = new File(args[0])
  sql = groovy.sql.Sql.newInstance(
      "jdbc:mysql://localhost/trip?autoReconnect=true",
      "grail",
      "holly",
      "com.mysql.jdbc.Driver")
    
  airports = new groovy.util.XmlParser().parse(f)
  airports.airport.each{airport ->
      println "${airport.@id} -- ${airport.name.text()}"
      sql.execute(
          "insert into airport (id, version, name, city, state, country, iata, lat, lng) values (?,?,?,?,?,?,?,?,?)",
            [airport.@id, 0, airport.name.text(), airport.city.text(),
                        airport.state.text(), airport.country.text(),
                        airport.iata.text(), airport.lat.text(),
                        airport.lng.text()]
         )
    }
} else {
    println "USAGE: restoreAirport [filename]"
}

GroovyのXML処理は随分と(Javaとは)変わっています。XmlParse.parseで返却されるオブジェクトは、XMLの要素名・属性名をプロパティとして持つようにコーディングできます。

米国の空港リストのデータベース化

記事では、米国地質調査所(USGS)が公開する米国内空港リストをGML(Geography Markup Language)フォーマットのXMLに変換して取り込む内容が記述されています。
ただし、USGSが公開する空港リストはシェープ形式(?)でXMLではないため、記事では、フリーの地理情報ソフトウェア(FWToolか?)の付属ツール(ogr2ogr)を使ってXMLに変換しています。

ということで、以下2つを行う必要がありました。

  1. USGSの米国空港リストをダウンロード
  2. FWToolsをダウンロードしインストール
USGSから米国空港リストのダウンロード

記事の「参考文献」にあるUSGS Airportsダウンロードのリンクをたどって、AirportsのShapefile:airprtx020.tar.gzをダウンロードします。
これを作業ディレクトリに展開します。

  • airprtx020.shp
  • airprtx020.shx
  • airprtx020.dbf
FWToolsのダウンロードとインストール

FWTools: Open Source GIS/RS Binary Kitには、Linux x86用とWindows用の2つのバイナリが置かれています。今回は、Linux上で動かすので、Linux用のものをダウンロードします。

ダウンロードしたFWTools-linux-2.0.6.tar.gzをとりあえずホームディレクトリで展開し、中にあるinstall.shを実行します。

~$ tar xfz FWTools-linux-2.0.6.tar.gz
~$ cd FWTools-2.0.6
FWTools-2.0.6$ ./install.sh
    :
FWTools-2.0.6$
USGSデータをGML形式(XML)に変換

ダウンロードしたUSGSの空港データを展開した場所で、FWToolsのogr2ogrコマンドを実行します。

work$ LD_LIBRARY_PATH=~/FWTools-2.0.6/lib ~/FWTools-2.0.6/bin/ogr2ogr -f "GML" airports.xml airprtx020.shp
work$

うまくいくと、airports.xmlが生成されます。

日本の空港に関する情報

米国の空港は上記の通りですが、日本の空港について情報がないかを調べてみました。日本の地理情報は、国土交通省国土計画局から「国土数値情報」として提供されています。空港に関する情報も、その中に含まれています。
http://nlftp.mlit.go.jp/ksj/jpgis/datalist/KsjTmplt-C28-v1_1.html

残念なことに、IATAおよびICAOの空港コードは上記データには含まれていないので、別途照合する必要があります。
IATAのコードは、IATAからは有償でないと入手できません。Wikipedia等の公開されている情報から集めることになりそうです。

ビューの生成

DataSource.groovyからdbCreate変数を削除

ドメインクラスの定義によって自動的にデータベース上のテーブルが調整されるのを防ぐため、grails-app/conf/DataSource.groovyからdbCreate変数を定義している箇所を削除します。

ビューの生成:エラー
Caused by: org.hibernate.MappingException: An association from the table flight refers to an unmapped class: Airport

よく考えると、今まで作ってきたディレクトリをそのまま使っているので、Flight, Airport, Airline, Tripのドメイン・クラスがそのまま存在しています。

そこで、いったん、Flight, Airport, Airline, Tripのドメインクラス、コントローラクラス、ビューを削除し、grails cleanで生成物を削除してから、再度 grails generate-all AirportMappingを実行します。記事に沿って、以下のGSPファイルを修正して実行します。

  • grails-app/views/airportMapping/list.gsp
  • grails-app/views/airportMapping/show.gsp

HibernateマッピングファイルによってレガシーJavaクラスを使用する方法

レガシーJava

リスト9のAirportHbm.javaを作成します。
まず、ソースの場所は、プロジェクトディレクトリ直下のsrc/java/org/davisworld/trip/AirportHbm.javaとなります。
次に、記事中ではリストの最後の行に以下のコメントがありますが、

// all of the other getters/setters go here

この意味を汲んで、ソースコード中にあるidのgetter/setter以外の、name, iata, state, lat, lngそれぞれについても記述しておきます。さもないと、後で実行したときにエラーが発生してしまいます。

レガシーJavaのシャドー

リスト10のAirportHbmConstraints.groovyは、上記リスト9のAirportHbm.javaと同じディレクトリに作成します。

コントローラクラス

通常のコントローラと同じく、grails-app/controllers/AirportHbmController.groovyに置きます。

HBMファイル

hibernate.cfg.xmlとAirportHbm.hbm.xmlは、ともにgrails-app/conf/hibernate/ディレクトリ下に置きます。

実行

サーバを再起動して実行します。

EJB3アノテーションを使用する

だんだん以前のファイルが散在し、何が必要で何が不要か見えなくなってきたので、新規にプロジェクトを作成し、ゼロから作成することにします。

プロジェクトの作成

名前が重なると問題発生時に切り分けが大変なので、新しいプロジェクト名"trip-davisworld"として作成します。

work$ grails create-app trip-davisworld
    :
Created Grails Application at /home/torutk/work/trip-davisworld
work$
レガシーJava "AirportHbm.java"を作成

trip-davisworld/src/java/org/davisworld/trip/AirportHbm.javaを作成します。前に作成したのをコピーしてもよいでしょう。

work$ cd trip-davisworld
trip-davisworld$ mkdir -p src/java/org/davisworld/trip
trip-davisworld$ cd src/java/org/davisworld/trip
trip$ cp ~/work/trip-planner/src/java/org/davisworld/trip/AirportHbm.java .
trip$
EJB3アノテーション付きJava "AirportAnnotation.java"の作成

リスト14の通りに、src/java/org/davisworld/trip/AirportAnnotation.javaを作成します。

  • リスト14の通りではダメでした。
org.hibernate.PropertyNotFoundException: Could not find a setter for property id in class org.davisworld.trip.AirportAnnotation

ということで、リスト14に対して一通りsetterメソッドを追加します。以下追加分のみ記述しています。

    public void setId(long anId) {
        id = anId;
    }

    public void setName(String aName) {
        name = aName;
    }

    public void setIata(String anIata) {
        iata = anIata;
    }

    public void setState(String aState) {
        state = aState;
    } 

    public void setLat(String aLat) {
        lat = aLat;
    }

    public void setLng(String aLng) {
        lng = aLng;
    }
シャドー"AirportAnnotationConstraints.groovy"の作成

リスト15の通りに、src/java/org/davisworld/trip/AirportAnnotationConstraints.groovyを作成します。

コントローラ"AirportAnnotationController.groovy"の作成

リスト16の通りに、grails-app/controllers/AirportAnnotationController.groovyを作成します。
grails create-controllerコマンドで雛形を生成してから編集してもよいでしょう。

trip-davisworld$ grails create-controller AirportAnnotation
  :
trip-davisworld$
Hibernate用のXMLファイルの作成

リスト17の通りにgrails-app/conf/hibernate/hibernate.cfg.xmlを作成します。

  • AirportHbm.hbm.xml

記事中に明記されていませんが、上述のhibernate.cfg.xmlで参照されているので、前の例題からコピーしておきます。

trip-davisworld$ cp ../trip-planner/gails-app/conf/hibernate/AirportHbm.hbm.xml grails-app/conf/hibernate/
trip-davisworld$
DataSource.groovyの修正

新規にプロジェクトを作成すると、DataSource.groovyは、Grails内蔵のHSQLDBを使用する設定となっています。MySQLを使用するように修正します。
また、記事にあるとおり、EJB3アノテーション検索設定を追記します。

  • grails-app/conf/DataSource.groovy
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration

dataSource {
  configClass = GrailsAnnotationConfiguration.class

  pooled = true
  driverClassName = "com.mysql.jdbc.Driver"
  username = "grail"
  password = "holly"
  url = "jdbc:mysql://localhost:3306/trip"
}
hibernate {
  cache.use_second_level_cache=true
  cache.use_query_cache=true
  cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}
// environment specific settings
environments {
  development {
    dataSource {
    }
  }
  test {
    dataSource {
    }
  }
  production {
    dataSource {
    }
  }
}

MySQLJDBCコネクタを、プロジェクト直下のlib下にコピーしておきます。

実行

いよいよ実行です。

http://localhost:8080/trip-davisworld