『Webアプリケーション構築入門』のデータベースアプリケーションをHeroku上で動かす方法


動作例

追記:無料で使えるHeroku Postgres Devは、行数が1万行に制限されることが発表されたので、別の方法(例:SQLiteを組み込む)を検討した方がいいでしょう。

何回かに分けて、拙著『Webアプリケーション構築入門 第2版』のJavaのウェブアプリをHeroku上で動かす方法を紹介してきました。

  1. MySQLからPostgreSQLへの移行
  2. Mavenの導入
  3. Herokuへのデプロイ

本質的な部分は以上で尽きているのですが、拙著の第9章で作成する郵便番号検索システム(Google Mapsとのマッシュアップ+Ajaxによるリアルタイム検索)を動作させるためには、Heroku上に郵便番号データベースを作らなければなりません。郵便番号データ程度の規模のデータをPaaSが無料(クレジットカード情報を入力せずに)で提供するデータベースにインポートするのは、面倒なことが多いのですが、Herokuも例外ではありません。ここでは、その方法を紹介します。

まず、Heroku Postgres Devを利用可能にします。本稿執筆時点で、このアドオンはクレジットカード情報を入力せずに利用できます。

以下のようなコマンドで、データベース情報を確認します。

heroku pg:info

この結果、データベース名が「HEROKU_POSTGRESQL_AQUA」だとわかったとします。そうしたら、以下のようなコマンドで、psqlで接続します(ファイアーウォールの中からだと接続できなくて、セキュアインターネット実験サービスでもだめだったりするから、後で何か考えないと・・・)。

heroku pg:psql HEROKU_POSTGRESQL_AQUA

テーブルを作り、データをインポートし、インデックスを張ります。に作成した/tmp/zip.csvをインポートしています。このファイルをHeroku側に転送しておく必要はありません。前に使った「COPY」は権限が無いため使えないので、代わりにpsqlのメタコマンドである「\copy」を使います。

CREATE TABLE zip (
  code CHAR(7) NOT NULL,
  address1 VARCHAR(10) DEFAULT '' NOT NULL,
  address2 TEXT NOT NULL,
  address3 TEXT NOT NULL,
  address4 TEXT NOT NULL,
  office TEXT NOT NULL
);

\copy zip FROM '/tmp/zip.csv' WITH CSV

CREATE INDEX code_idx ON zip.code;

\q

最初から使えるSHARED-DATABASEの代わりに、データベースをデフォルトに設定します。

heroku pg:promote HEROKU_POSTGRESQL_AQUA

これで郵便番号データベースは完成です。すでに紹介したアプリの配備方法を終わらせれば、拙著の郵便番号システムがHeroku上で動きます。

『Webアプリケーション構築入門』のウェブアプリをHeroku上で動かす方法

拙著『Webアプリケーション構築入門 第2版』では、PHPとJavaでウェブアプリを作る方法を解説しています。作ったウェブアプリを公開しようとするとき、PHPのウェブアプリはたいていのレンタルサーバで動くのでいいのですが、Javaのウェブアプリはほとんどのレンタルサーバでは動かないので、自分のサーバを持っていない場合はいろいろ面倒でした。

クラウド(IaaS)やVPSの普及によって、Javaのウェブアプリを公開するための経済的なハードルは下がりましたが、「レンタルサーバにファイルをコピーするだけでOK」というPHPに比べると、IaaSやVPSでの「サーバに各種サーバソフトウェアをインストールして・・・」という技術的ハードルはあまり下がりませんでした。

しかし最近(と言うほどでもありませんか)、HerokuのようなJavaも使えるクラウド(PaaS)の登場によって、Javaのウェブアプリを公開するための技術的ハードルも、ずいぶん下がってきたような気がします。

というわけで、拙著のJavaのウェブアプリを、できるだけ少ない作業で、クレジットカードを使わずにHerokuに移植する方法を紹介します。作業が必要な理由は以下の通りです。

  1. Herokuでは、Mavenプロジェクトを使うことが推奨されるから
  2. Herokuでは、無料で利用できるDBMSがMySQLではなくPostgreSQLだから
  3. Herokuでは、GlassFishではなく組み込みコンテナ(TomcatやJetty-Runner)を使うから
  4. Herokuには、Gitでプッシュしなければならないから

Getting Started with Herokuのstep 3までを済ませてから先に進んでください。

Maven

Mavenは、記事「『Webアプリケーション構築入門』のウェブアプリをMavenで管理する方法」のように導入できます。プロジェクトを作成したら、Herokuに不要な要素をpom.xmlから削除し、Commons Langを追加しておきましょう(この記事の最後に掲載したpom.xmlを参考に)。

PostgreSQLへの対応

追記:無料で使えるデータベースには、サイズや行数の制限があるので、郵便番号検索システムのためには別の方法(例:SQLiteを組み込む)を検討した方がいいでしょう。

PostgreSQLへの対応は、以前書いた「『Webアプリケーション構築入門』の郵便番号検索でPostgreSQLを使う方法(Javaの場合)」という記事で半分終わっています。JDBCを使えるようにし、データベースへの接続方法をHerokuにあわせれば完了です。

Mavenを使っているので、MySQLの場合と同様に、pom.xmlに以下のように記述すればJDBCが使えるようになります。拙著8.2.2項のように、ライブラリを手動で導入する必要はありません。

<dependency>
  <groupId>postgresql</groupId>
  <artifactId>postgresql</artifactId>
  <version>8.3-603.jdbc4</version>
</dependency>

Herokuでは、データベースへのアクセス情報を環境変数から取得することになっているので、データベースに接続する部分は次のように書き換えます(参照:Using the DATABASE_URL in plain JDBC)。

//データベースに接続
java.net.URI dbUri = new java.net.URI(System.getenv("DATABASE_URL"));
String username = dbUri.getUserInfo().split(":")[0];
String password = dbUri.getUserInfo().split(":")[1];
String dbUrl = "jdbc:postgresql://" + dbUri.getHost() + dbUri.getPath() + ":" + dbUri.getPort();
Connection conn = DriverManager.getConnection(dbUrl, username, password);

組み込みコンテナ

拙著では、ウェブアプリのコンテナとしてGlassFishを使っていますが、Herokuでは、組み込みのTomcatJettyを使います。ここでは、ソースコードの修正が不要なJetty Runnerを使いましょう。

maven-dependency-pluginの部分を次のように書き換えます。

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

コンソールで次のように入力し、ビルドします。NetBeans上で、プロジェクト名を右クリック→「構築」としてもかまいません。PostgreSQLのデータベースを「mydb」、パスワードを「pass」と仮定しています。

cd ~/NetBeansProjects/プロジェクト名
mvn package

ローカルでの動作確認

ローカルでの動作確認は次のように行います。NetBeansからGlassFishを起動している場合は、出力タブの「GlassFish 3.1.2」等にある停止ボタンをクリックして、NetBeans上のGlassFishは止めておきます(あるいはGlassFishとは別のポートを指定する)。JSPがある場合は、JREのjavaではなく、JDKのjavaで実行しなければなりません(PATHの設定を変えるのが正しい方法です)。

export DATABASE_URL=postgres://postgres:pass@localhost/mydb
/usr/lib/jvm/java-7-openjdk-i386/bin/java -jar target/dependency/jetty-runner.jar --port 8080 target/*.war

http://localhost:8080/HelloServletのようなURLで、ウェブアプリにアクセス出来れば完成です。

NetBeansで実行するときもこのコードが動くようにするためには、上記の環境設定(DATABASE_URL)のもと、次のようにしてNetBeansを起動するといいでしょう(NetBeansのバージョンに依存します)。

~/netbeans-7.1.2/bin/netbeans &

Gitでプッシュする方法

次のような内容でウェブプロセスを定義するファイルProcfileを、pom.xmlがあるディレクトリに作ります。

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

Gitリポジトリを作成し、プロジェクトをインポートします(NetBeans上で、チーム→Git→初期化、チーム→Git→コミットとしてもかまいません)。

git init
echo target > .gitignore
git add .
git commit -m "Ready to deploy"

Herokuのアプリを作ります。

heroku create 好きな名前

NetBeansで作業する場合は、このときに表示されるGitリポジトリの場所、「git@heroku.com:名前.git」のような文字列が必要になります(Herokuのウェブサイトで確認できるので、記録する必要はありません)。

Herokuにプッシュします(NetBeansで、Git→リモート→プッシュとしてもかまいません。Gitリポジトリの場所を指定してください。このあたり、説明が雑で申し訳ないです)。

git push heroku master

次のような表示になれば成功です。

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

heroku open」とするとブラウザが起動するので、ウェブアプリが正しく動作することを確認してください。

うまく動作しないときは、「heroku logs」でログを確認するといいでしょう。

Herokuのプロセスは、「heroku ps」で確認できます。今の例では、次のような結果になるはずです。

=== web: `java $JAVA_OPTS -jar target/dependency/jetty-runner.jar --port $PORT target/*.war`
web.1: up for 4m

何も出力されていないときは、「heroku scale web=1」などとしてプロセスを起動してください。

すでにプッシュしたプロジェクトをNetBeansに新たに読み込むときは、チーム→Git→クローンとし、リポジトリURLと、Getting Started with Herokuで作成したはずの非公開鍵ファイル(~/.ssh/id_rsa)を指定してください。

最後に、この記事のために作成したpom.xmlを掲載しておきます。


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mycompany</groupId>
  <artifactId>*****</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>*****</name>

  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.3</version>
    </dependency>
    <dependency>
      <groupId>postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>8.3-603.jdbc4</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.1.1</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>2.4</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
              <artifactItems>
                <artifactItem>
                  <groupId>org.mortbay.jetty</groupId>
                  <artifactId>jetty-runner</artifactId>
                  <version>8.1.4.v20120524</version>
                  <destFileName>jetty-runner.jar</destFileName>
                </artifactItem>
              </artifactItems>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

『Webアプリケーション構築入門』の郵便番号検索でPostgreSQLを使う方法(Javaの場合)

追記:無料で使えるHeroku Postgres Devは、行数が1万行に制限されることが発表されたので、別の方法(例:SQLiteを組み込む)を検討した方がいいでしょう。

拙著『Webアプリケーション構築入門』では、開発プラットフォームとしてJavaとPHPを採用しており、ウェブアプリを実際に公開するときは、自前のサーバ(VPSを含む)を使える人にはJava・PHPの好きな方を、レンタルサーバを使う人にはPHPを使って貰うつもりで執筆していました。

最近、自前のサーバでもレンタルサーバでもない第3の選択肢としてのPaaSが普及してきており、その一部は無料で使えるようになっています。「サーバ管理技術」よりも「アプリケーション開発」に興味がある人にとっては、自前のサーバやレンタルサーバよりPaaSの方が便利でしょう。しかし、拙著はPaaSを意識した書き方にはなっていないので、そのあたりの情報をアフターサービスとして提供していきたいと考えている今日この頃です。

Javaを無料で使えるPaaSの一つにHerokuがあります。Javaが使えるレンタルサーバはあまりないので(私が知らないだけ?)、そういう意味でもHerokuは魅力的なのですが、無料で使えるデータベース管理システム(DBMS)が、拙著で採用しているMySQLではなくPostgreSQLだというのが、初心者には障害になるかもしれません。この障害を回避するために、拙著の第9章「ウェブアプリの実例」で作成する「郵便番号検索システム(Google MapsとのマッシュアップやAjaxによるリアルタイム検索を含む)」でPostgreSQLを使う方法を紹介しましょう。

まずPostgreSQLをインストールします。

sudo apt-get install postgresql

PostgreSQLの管理者postgresのパスワードを適当に設定します(例:pass)。

sudo passwd postgres
#ここでパスワードを入力

郵便番号辞書データベースを作ります。拙著9.1.1項のとおりにファイルをダウンロードして、次のコマンドで必要なデータだけを集めたCSVファイルを作ります。各コマンドの意味は「man gawk」などとして確認してください。

nkf -Lu -w ken_all.csv | gawk -F, -v OFS="," '{print $3,$7,$8,$9,"\"\"","\"\""}'  > /tmp/zip.csv
nkf -Lu -w jigyosyo.csv | gawk -F, -v OFS="," '{print $8,$4,$5,$6,$7,$3}' >> /tmp/zip.csv

管理者になって、データベースmydbを作ります。

su postgres
#ここでパスワードを入力

createdb mydb

MySQLのコマンド「mysql」に相当するコマンド「psql」を実行します。

psql mydb

テーブルを作成し、先に作成したCSVファイルをインポートします(拙著9.1.2項とは異なり、必要なデータだけをインポートすることにします)。

CREATE TABLE zip (
  code CHAR(7) NOT NULL,
  address1 VARCHAR(10) DEFAULT '' NOT NULL,
  address2 TEXT NOT NULL,
  address3 TEXT NOT NULL,
  address4 TEXT NOT NULL,
  office TEXT NOT NULL
);

COPY zip FROM '/tmp/zip.csv' WITH CSV;

インデックスを作成します。

CREATE INDEX code_idx ON zip (code);

Webアプリから接続する際のパスワードを設定します。これは、先に設定したOSのパスワードと同じである必要はありません。

ALTER USER postgres WITH PASSWORD 'pass';

「\q」と入力してコマンドpsqlを終了します。

拙著8.2.2項「Javaからデータベースへのアクセス」の場合と同様に、「PostgreSQL JDBC ドライバ」を追加します。

コードの変更はzip.jsp(p. 134あるいはサポートページを参照)のデータベース接続部分だけです。次のように書き換えます。ここで使うパスワードは、先のALTER USER文で設定したパスワードです。

//データベースに接続(Heroku非対応)
Class.forName("org.postgresql.Driver").newInstance();
String url = "jdbc:postgresql://localhost/mydb";
Connection conn = DriverManager.getConnection(url, "postgres", "pass");

以上の準備で、拙著第9章の郵便番号検索システムはPostgreSQLで動くようになります。このままではまだHerokuでは動きませんが、とりあえず、ローカルで動くようになったので一区切りです。

オープンソースデータベース標準教科書 -PostgreSQL-などというものもありますね。