Getting Started with Spring MVC Hibernate on Heroku/Cedar をためしてみてる

昨日に引き続きjavaでherokuと戯れてみる。
今回は Getting Started with Spring MVC Hibernate on Heroku | Heroku Dev Center をやってみる。

Prerequisites

java,maven.git,heroku client, foreman が必要とのこと。これらは昨日のエントリでいれたのでOK。
あと、ローカルでのテストのためにPostgresが必要とのこと。このあたりを参考にした。

$ sudo port install postgresql90
$ sudo port install postgresql90-server

9/12に9.1がリリースされてるが、MacPortsだとまだALPHA RELEASEっぽいので9.0にした。
データ領域を作成。

$ sudo mkdir -p /opt/local/var/db/postgresql90/defaultdb
$ sudo chown postgres:postgres /opt/local/var/db/postgresql90/defaultdb
$ sudo su postgres -c '/opt/local/lib/postgresql90/bin/initdb -D /opt/local/var/db/postgresql90/defaultdb'

で、起動。

$ sudo su postgres -c '/opt/local/lib/postgresql90/bin/postgres -D /opt/local/var/db/postgresql90/defaultdb'

Create a Spring MVC Hibernate app

Spring MVC Hivernate アプリは Spring Roo を使うととても簡単につくれるよ!
っぽいことが書かれている。SpringもHibernateもきちんとさわったことないので Option2 でいくことにする。
ちなみに Option1 はサンプルコードを git clone せよとのこと。

Option 2. Create the App Using Spring Roo

Spring Roo をインストールせよ。とのことなのでインストールしてみる。
Community Downloads | SpringSource.orgにLastest GA release 1.1.5.RELEASE というのがあるのでこれをDL。
このあたりをみると解凍して bin/roo.sh をPATHに追加すればいいっぽい。
今回は bin/roo.sh のシンボリックリンクを /usr/local/bin においてみた。

$ sudo ln -s ~/Library/Java/spring-roo-1.1.5.RELEASE/bin/roo.sh /usr/local/bin/roo

roo を実行するとこんなかんじ。rooのプロンプトが現れる。exitで抜けられる。

$ roo
    ____  ____  ____  
   / __ \/ __ \/ __ \ 
  / /_/ / / / / / / / 
 / _, _/ /_/ / /_/ /  
/_/ |_|\____/\____/    1.1.5.RELEASE [rev d3a68c3]


Welcome to Spring Roo. For assistance press TAB or type "hint" then hit ENTER.
                                                                        
At this time you have not authorized Spring Roo to download an index of
available add-ons. This will reduce Spring Roo features available to you.
Please type 'download status' and press ENTER for further information.

roo>

さて、roo がインストールできたところでアプリケーションを作成していく。
まず、今回のアプリは petclinic らしいのでアプリのディレクトリを作成する。

$ mkdir petclinic && cd petclinic

で、roo でアプリを生成せよとのこと。以下を実行。

$ roo script --file clinic.roo

大量のログとともにアプリのファイルを生成してるっぽい。
実行後のディレクトリ構造はこんな感じ。

$ tree -a
.
├── log.roo
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── springsource
    │   │           └── petclinic
    │   │               ├── domain
    │   │               │   ├── AbstractPerson.java
    │   │               │   ├── AbstractPerson_Roo_Configurable.aj
    │   │               │   ├── AbstractPerson_Roo_Entity.aj
    │   │               │   ├── AbstractPerson_Roo_JavaBean.aj
    │   │               │   ├── AbstractPerson_Roo_ToString.aj
    │   │               │   ├── Owner.java
    │   │               │   ├── Owner_Roo_Configurable.aj
    │   │               │   ├── Owner_Roo_Entity.aj
    │   │               │   ├── Owner_Roo_JavaBean.aj
    │   │               │   ├── Owner_Roo_ToString.aj
    │   │               │   ├── Pet.java
    │   │               │   ├── Pet_Roo_Configurable.aj
    │   │               │   ├── Pet_Roo_Entity.aj
    │   │               │   ├── Pet_Roo_Finder.aj
    │   │               │   ├── Pet_Roo_JavaBean.aj
    │   │               │   ├── Pet_Roo_ToString.aj
    │   │               │   ├── Vet.java
    │   │               │   ├── Vet_Roo_Configurable.aj
    │   │               │   ├── Vet_Roo_Entity.aj
    │   │               │   ├── Vet_Roo_JavaBean.aj
    │   │               │   ├── Vet_Roo_ToString.aj
    │   │               │   ├── Visit.java
    │   │               │   ├── Visit_Roo_Configurable.aj
    │   │               │   ├── Visit_Roo_Entity.aj
    │   │               │   ├── Visit_Roo_Finder.aj
    │   │               │   ├── Visit_Roo_JavaBean.aj
    │   │               │   └── Visit_Roo_ToString.aj
    │   │               ├── reference
    │   │               │   ├── PetType.java
    │   │               │   └── Specialty.java
    │   │               └── web
    │   │                   ├── ApplicationConversionServiceFactoryBean.java
    │   │                   ├── ApplicationConversionServiceFactoryBean_Roo_ConversionService.aj
    │   │                   ├── OwnerController.java
    │   │                   ├── OwnerController_Roo_Controller.aj
    │   │                   ├── PetController.java
    │   │                   ├── PetController_Roo_Controller.aj
    │   │                   ├── PetController_Roo_Controller_Finder.aj
    │   │                   ├── VetController.java
    │   │                   ├── VetController_Roo_Controller.aj
    │   │                   ├── VisitController.java
    │   │                   ├── VisitController_Roo_Controller.aj
    │   │                   └── VisitController_Roo_Controller_Finder.aj
    │   ├── resources
    │   │   ├── META-INF
    │   │   │   ├── persistence.xml
    │   │   │   └── spring
    │   │   │       ├── applicationContext.xml
    │   │   │       └── database.properties
    │   │   └── log4j.properties
    │   └── webapp
    │       ├── WEB-INF
    │       │   ├── classes
    │       │   │   ├── alt.properties
    │       │   │   └── standard.properties
    │       │   ├── i18n
    │       │   │   ├── application.properties
    │       │   │   ├── messages.properties
    │       │   │   ├── messages_de.properties
    │       │   │   └── messages_es.properties
    │       │   ├── layouts
    │       │   │   ├── default.jspx
    │       │   │   └── layouts.xml
    │       │   ├── spring
    │       │   │   └── webmvc-config.xml
    │       │   ├── tags
    │       │   │   ├── form
    │       │   │   │   ├── create.tagx
    │       │   │   │   ├── dependency.tagx
    │       │   │   │   ├── fields
    │       │   │   │   │   ├── checkbox.tagx
    │       │   │   │   │   ├── column.tagx
    │       │   │   │   │   ├── datetime.tagx
    │       │   │   │   │   ├── display.tagx
    │       │   │   │   │   ├── editor.tagx
    │       │   │   │   │   ├── input.tagx
    │       │   │   │   │   ├── reference.tagx
    │       │   │   │   │   ├── select.tagx
    │       │   │   │   │   ├── simple.tagx
    │       │   │   │   │   ├── table.tagx
    │       │   │   │   │   └── textarea.tagx
    │       │   │   │   ├── find.tagx
    │       │   │   │   ├── list.tagx
    │       │   │   │   ├── show.tagx
    │       │   │   │   └── update.tagx
    │       │   │   ├── menu
    │       │   │   │   ├── category.tagx
    │       │   │   │   ├── item.tagx
    │       │   │   │   └── menu.tagx
    │       │   │   └── util
    │       │   │       ├── language.tagx
    │       │   │       ├── load-scripts.tagx
    │       │   │       ├── pagination.tagx
    │       │   │       ├── panel.tagx
    │       │   │       ├── placeholder.tagx
    │       │   │       └── theme.tagx
    │       │   ├── views
    │       │   │   ├── dataAccessFailure.jspx
    │       │   │   ├── footer.jspx
    │       │   │   ├── header.jspx
    │       │   │   ├── index-template.jspx
    │       │   │   ├── index.jspx
    │       │   │   ├── menu.jspx
    │       │   │   ├── owners
    │       │   │   │   ├── create.jspx
    │       │   │   │   ├── list.jspx
    │       │   │   │   ├── show.jspx
    │       │   │   │   ├── update.jspx
    │       │   │   │   └── views.xml
    │       │   │   ├── pets
    │       │   │   │   ├── create.jspx
    │       │   │   │   ├── findPetsByNameAndWeight.jspx
    │       │   │   │   ├── findPetsByOwner.jspx
    │       │   │   │   ├── findPetsBySendRemindersAndWeightLessThan.jspx
    │       │   │   │   ├── findPetsByTypeAndNameLike.jspx
    │       │   │   │   ├── list.jspx
    │       │   │   │   ├── show.jspx
    │       │   │   │   ├── update.jspx
    │       │   │   │   └── views.xml
    │       │   │   ├── resourceNotFound.jspx
    │       │   │   ├── uncaughtException.jspx
    │       │   │   ├── vets
    │       │   │   │   ├── create.jspx
    │       │   │   │   ├── list.jspx
    │       │   │   │   ├── show.jspx
    │       │   │   │   ├── update.jspx
    │       │   │   │   └── views.xml
    │       │   │   ├── views.xml
    │       │   │   └── visits
    │       │   │       ├── create.jspx
    │       │   │       ├── findVisitsByDescriptionAndVisitDate.jspx
    │       │   │       ├── findVisitsByDescriptionLike.jspx
    │       │   │       ├── findVisitsByVisitDateBetween.jspx
    │       │   │       ├── list.jspx
    │       │   │       ├── show.jspx
    │       │   │       ├── update.jspx
    │       │   │       └── views.xml
    │       │   └── web.xml
    │       ├── images
    │       │   ├── add.png
    │       │   ├── banner-graphic.png
    │       │   ├── create.png
    │       │   ├── de.png
    │       │   ├── delete.png
    │       │   ├── en.png
    │       │   ├── es.png
    │       │   ├── favicon.ico
    │       │   ├── list.png
    │       │   ├── resultset_first.png
    │       │   ├── resultset_last.png
    │       │   ├── resultset_next.png
    │       │   ├── resultset_previous.png
    │       │   ├── show.png
    │       │   ├── springsource-logo.png
    │       │   └── update.png
    │       ├── selenium
    │       │   ├── test-owner.xhtml
    │       │   ├── test-pet.xhtml
    │       │   ├── test-suite.xhtml
    │       │   ├── test-vet.xhtml
    │       │   └── test-visit.xhtml
    │       └── styles
    │           ├── alt.css
    │           └── standard.css
    └── test
        ├── java
        │   └── com
        │       └── springsource
        │           └── petclinic
        │               └── domain
        │                   ├── OwnerDataOnDemand.java
        │                   ├── OwnerDataOnDemand_Roo_Configurable.aj
        │                   ├── OwnerDataOnDemand_Roo_DataOnDemand.aj
        │                   ├── OwnerIntegrationTest.java
        │                   ├── OwnerIntegrationTest_Roo_Configurable.aj
        │                   ├── OwnerIntegrationTest_Roo_IntegrationTest.aj
        │                   ├── PetDataOnDemand.java
        │                   ├── PetDataOnDemand_Roo_Configurable.aj
        │                   ├── PetDataOnDemand_Roo_DataOnDemand.aj
        │                   ├── PetIntegrationTest.java
        │                   ├── PetIntegrationTest_Roo_Configurable.aj
        │                   ├── PetIntegrationTest_Roo_IntegrationTest.aj
        │                   ├── VetDataOnDemand.java
        │                   ├── VetDataOnDemand_Roo_Configurable.aj
        │                   ├── VetDataOnDemand_Roo_DataOnDemand.aj
        │                   ├── VetIntegrationTest.java
        │                   ├── VetIntegrationTest_Roo_Configurable.aj
        │                   ├── VetIntegrationTest_Roo_IntegrationTest.aj
        │                   ├── VisitDataOnDemand.java
        │                   ├── VisitDataOnDemand_Roo_Configurable.aj
        │                   ├── VisitDataOnDemand_Roo_DataOnDemand.aj
        │                   ├── VisitIntegrationTest.java
        │                   ├── VisitIntegrationTest_Roo_Configurable.aj
        │                   └── VisitIntegrationTest_Roo_IntegrationTest.aj
        └── resources

かなりのファイル数があるな・・・でもpom.xmlがあったりmavenのディレクトリ構造と同じだったりするのでなんとなくわかるな。

デフォルトのDBがHypersonicのインメモリDBらしい。production環境と同じDB使うほうが絶対いいよといっているっぽいので postgres にスイッチする。

$ roo persistence setup --provider HIBERNATE --database POSTGRES
    ____  ____  ____  
   / __ \/ __ \/ __ \ 
  / /_/ / / / / / / / 
 / _, _/ /_/ / /_/ /  
/_/ |_|\____/\____/    1.1.5.RELEASE [rev d3a68c3]


Welcome to Spring Roo. For assistance press TAB or type "hint" then hit ENTER.
Updated SRC_MAIN_RESOURCES/META-INF/spring/database.properties
Please update your database details in src/main/resources/META-INF/spring/database.properties.
Updated ROOT/pom.xml [removed dependency org.hsqldb:hsqldb:1.8.0.10; added dependency postgresql:postgresql:8.4-702.jdbc3]
Updated SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
Updated SRC_MAIN_RESOURCES/META-INF/persistence.xml
                                                                        
At this time you have not authorized Spring Roo to download an index of
available add-ons. This will reduce Spring Roo features available to you.
Please type 'download status' and press ENTER for further information.

ちょっと脱線だが、最後の3行はよく見かけるので言われるとおり download status を実行し、指示に従い了承っぽいことをした。どうやらリソースをダウンロードするには必要なようだ。

で、roo で生成したファイルを git にコミットする。

$ git init
$ echo target > .gitignore
$ git add .
$ git commit -m "init"

Modify Database Configuration

roo で生成したファイルにはDBへの接続情報が書かれているが、ハードコードされているらしい。(ここらしい→ src/main/resources/META-INF/spring/database.properties)
ハードコードはイクナイので環境変数から読み込むようにしよう。っぽいことが書かれている。
環境変数名は"DATABASE_URL"の模様。で、この変数のフォーマットはこんなかんじ。

postgres://user:password@hostname/path

で、そのために src/main/resources/META-INF/spring/applicationContext.xml を編集する。以下の bean 定義を追加と、

<bean class="java.net.URI" id="dbUrl">
    <constructor-arg value="${DATABASE_URL}"/>
</bean>

以下のように id="dataSource" 要素内の property 定義を書き換える。

<bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">
    <property name="driverClassName" value="${database.driverClassName}"/>
    <property name="url" value="#{ 'jdbc:postgresql://' + @dbUrl.getHost() + @dbUrl.getPath() }"/>
    <property name="username" value="#{ @dbUrl.getUserInfo().split(':')[0] }"/>
    <property name="password" value="#{ @dbUrl.getUserInfo().split(':')[1] }"/>
    ...

で、実行するために環境変数"DATABASE_URL"がいるので定義する。

export DATABASE_URL=postgres://scott:tiger@localhost/myapp

Add Jetty Runner

Jettyで実行するために、Jetty-runner のライブラリを target ディレクトリに置く必要があるっぽい。
それを実現するために、maven-dependency-plugin を使う模様。
以下を pom.xml 内の plugins セクションの最後に追加する。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals><goal>copy</goal></goals>
            <configuration>
                <artifactItems>
                    <artifactItem>
                        <groupId>org.mortbay.jetty</groupId>
                        <artifactId>jetty-runner</artifactId>
                        <version>7.4.5.v20110725</version>
                        <destFileName>jetty-runner.jar</destFileName>
                    </artifactItem>
                </artifactItems>
            </configuration>
        </execution>
    </executions>
</plugin>

Declare Process Types With Foreman/Procfile

昨日のエントリとほぼ同じ。Procfileを用意する。
が、違うところは jetty-runner で war ファイルを渡しているところか。これで webアプリを jetty 上で動かせるということか。

web: java $JAVA_OPTS -jar target/dependency/jetty-runner.jar --port $PORT target/*.war

Run Your App Locally

Build Your App

では、ビルドしてみよう。

$ mvn package

んー、ビルドでエラーがでるな。どうやらテストでエラーな模様。Postgresの標準出力に以下のようなログが多数。

FATAL: role "scott" does not exist

Postgres のユーザとか一切作ってないからっぽい。postgres が起動している状態で以下を行った。

$ /opt/local/lib/postgres90/bin/psql -U postgres
postgres=# create user scott;
postgres=# \password scott
Enter new password: 
Enter it again: 
postgres=# \q

もっかい動かしてみると以下のログに変わった。

FATAL: database "myapp" does not exist

どうやら、databaseをつくっていないからっぽい。で、以下を行った。

$ /opt/local/lib/postgresql90/bin/createdb -U postgres myapp

で、もっかい mvn package やったらテスト通った!ビルド成功!

Start Your App With Foreman/Procfile

起動してみる。

$ foreman start

で、 http://localhost:5000 へアクセス。おー、Rooの画面が表示された!

Test it

画面からデータも登録できたっぽい。いいかんじ。

Deploy to Heroku/Cedar

さぁ、herokuへデプロイ。
まず、これまでの変更をコミット。

$ git add .
$ git commit -m "Ready to deploy"

herokuのCedarスタックも作成する。

$ heroku create --stack cedar

heroku へデプロイ!

$ git push heroku master

ビルドも成功!

$ heroku open

で、画面も確認できたしデータも登録できた!

まとめ

  • 久しぶりに postgres さわったので思い出すのにたいへんだった。かなり時間かかったな・・・
  • roo はよくわからん。おぼえるのたいへんそうかも。。。
  • アプリの起動は java コマンドを起動できればなんでもいいみたい。前みたいに組み込みでなくてもいいのね。
  • そういえば hibernate はほとんどみかけなかったな・・。DBのスキーマ作成とかはいいかんじでやってくれてるのだろうか?rails でいう db:migrate 的なこともしてないのになぁ。

と、本題とは関係ないところでえらくはまってしまった。
今度は addons あたりを調べてみようか。