SQLite for Java on Heroku


拙著『Webアプリケーション構築入門 第2版』は、ウェブアプリ構築の入門書です。サーバ・サイドプログラムは、JavaとPHPで作成しており、読者は自分の好きな方を選んで読み進められるようになっています。

PHPで作ったウェブアプリはレンタルサーバとかで簡単に公開できるけど、Javaで作ったウェブアプリはちょっと面倒でした。

しかし、HerokuならJavaが動くので、これを使えば簡単です。このことについて、かつて書きました。

しかし、確かにJavaは動きますが、データベース管理システムは拙著で採用したMySQLではなくPostgreSQLなので、PostgreSQLへの対応が必要です。このことについて、かつて書きました。

しかし、拙著のサンプルアプリである郵便番号検索システムのためのデータベースは比較的大きく、ウェブアプリをPostgreSQLに対応させただけでは、データのインポートが面倒で使えません。この問題は、Postgres dev planという無料のアドオンで解決するのが簡単です。このことについて、かつて書きました。

しかし、これだけ書いた後で、「Postgres dev planでは、1つのテーブルに1万行までしか入れられないようにする予定です」というアナウンスがありました。郵便番号データベースには、14万行以上のデータがあるので、Postgres dev planではダメになるわけです。

月9ドルのBasic planに移行すれば問題は解決するのですが、そんなものを学生に勧められるわけもなく、もう面倒くさいのでSQLiteを組み込んで使うことにします。さらばPostgreSQL。

必要な作業は3つです。

  1. データベースファイルの作成
  2. SQLiteのためのライブラリの導入
  3. データベースへの接続方法の修正

データベースファイルの作成

FirefoxのアドオンであるSQLite Managerを使うのが簡単です。

  • アドオンをインストールする
  • ツール→SQLite Manager
  • pom.xmlがあるのと同じディレクトリにmydb.sqliteを作成する
  • デーブルを作る
  • 郵便番号データをインポートする

テーブルは次のようなSQL文を実行して作成します。

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
);

CREATE INDEX code_idx ON zip (code)

データのインポートには、以前作成した/tmp/zip.csvを使えばいいでしょう。

SQLiteのためのライブラリの導入

pom.xmlに、次のような要素を追記します。

<dependency>
  <groupId>org.xerial</groupId>
  <artifactId>sqlite-jdbc</artifactId>
  <version>3.7.2</version>
</dependency>

データベースへの接続方法の修正

zips.jspを次のように修正します。

//データベースに接続
Class.forName("org.sqlite.JDBC");
Connection conn=DriverManager.getConnection("jdbc:sqlite:mydb.sqlite");

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


動作例

『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>

Twitter4Jでつぶやく練習


拙著『Webアプリケーション構築入門』では、「Webアプリを作れるようになる前に、Webアプリを使えるようになるのが大事」という方針のもと、比較的早い段階で、JavaとPHP、JavaScriptでウェブAPIを利用する方法を紹介しています。

そこで採用したウェブAPIの一つがTwitter APIですが、入門書であることと、紙面が限られていることを理由に、OAuth認証なしでも使えるPublic Timelineだけしか紹介しませんでした。

OAuth認証を使ってつぶやく方法は、このブログで以前紹介しましたが(該当記事)、その後、Mavenを紹介したり(該当記事)、Twitterのサイト上で自分用のトークンを作れるようになったりしたので、「プログラムからつぶやく方法」を改めて書いておきましょう。

Twitterアプリの準備

  1. まず、Twitter DevelopersにTwitterアカウントでログインし、「Create a new app」をクリック、アプリケーションを登録します(コールバックURLはあとで変えられるので適当に入れておけばいいでしょう。今回は使いませんが)。
  2. つぶやくためにはWrite権限が必要なので、SettingsタブでApplication Typeを「Read & Write」に変更します。
  3. Detailsタブに戻って、「Create my access token」をクリックし、Access tokenとAccess token secretを生成します。このページに書かれているConsumer keyとConsumer secretも使います。

Mavenプロジェクトの作成

Mavenプロジェクトを作成し、pom.xmlのdependencies要素の中に次のように記述します(TwitterのためのライブラリであるTwitter4Jを使います)。(参考:『Webアプリケーション構築入門』のウェブアプリをMavenで管理する方法

<dependency>
  <groupId>org.twitter4j</groupId>
  <artifactId>twitter4j-core</artifactId>
  <version>[2.2,)</version>
</dependency>

つぶやくためのコード

つぎのようなコードでつぶやきます。

import twitter4j.*;
import twitter4j.conf.*;

public class Tweet {

  public static void main(String[] args) throws Exception {
    ConfigurationBuilder cb = new ConfigurationBuilder();
    cb.setDebugEnabled(true)
            .setOAuthConsumerKey(**Consumer key**)
            .setOAuthConsumerSecret(**Consumer secret**)
            .setOAuthAccessToken(**Access token**)
            .setOAuthAccessTokenSecret(**Access token secret**);
    TwitterFactory tf = new TwitterFactory(cb.build());
    Twitter twitter = tf.getInstance();
    twitter.updateStatus("テスト at " + (new java.util.Date()));
  }
}

『オイラーの贈物』のバグ


吉田武『素数夜曲 女王陛下のLISP』(東海大学出版会)が発売されて一部で話題になっている今日この頃ですが、この900ページ近い大著についてはまだコメントできる段階ではないので、この機会に思い出した同じ著者の『オイラーの贈物』のバグを報告しようと思います。この本を貶めるのではなく、この本のほんの少しの断片でさえもいろいろ調べて楽しむ題材になるということを伝えるのが目的です。

高校生のための数学入門書『オイラーの贈物』

『新装版 オイラーの贈物』第1版第7刷の付録A p. 314 には、モンテカルロ法を用いて円周率の近似値を求める、次のようなUBASICのコードが掲載されています(参照:UBASICの動かし方

10   randomize
20   input "10^n=";n
30   for i=1 to n
40   k=0:j=0
50   for j=1 to 10^i-1
60   x=rnd:y=rnd:if x*x+y*y<=1 then k=k+1
70   next j
80   print 10^i;"点中";k;"個命中";"Pi=";4*k/j
90   next i

このコードには、1つのバグといくつかの問題があります。

バグ

50行目のfor文で、j1から(10^i-1)まで動くので、モンテカルロシミュレーションの試行回数は(10^i-1)回になります。しかし、80行目では試行回数を10^iあるいはj(ループが終了した時点でj==10^i)と、1回多く数えています。これにあわせるためには、50行目を次のように修正しなければなりません。

50   for j=0 to 10^i-1

問題

プロンプトがおかしい、とかいう細かい話はおくとして、

問題(無駄な計算)

50行目のfor文が終わると(80行目の直前)jの値は10^iになっているので、80行目で10^iを再度計算する必要はありません。80行目の末尾では、10^iの意味でjを使っているので、先頭でもそれを採用するといいでしょう。つまり、80行目は次のように書いた方がいいでしょう。

80   print j;"点中";k;"個命中";"Pi=";4*k/j

問題(無駄な代入)

40行目の「:j=0」は、その直後に「j=1」があるので無駄です。しかし、次の問題の解決に必要なので、そのままにしておいてもいいでしょう。

問題(for文の範囲)

UBASICの仕様はよくわかりませんが、手元のバージョン8.8fでは、for文の範囲(上限ではない)が2^32を超えると「数値が大き過ぎます」というエラーが出ます。ですから、せっかく大きな桁数をサポートするUBASICを使っても、このプログラムでは試行回数10^9回までしかシミュレートできません。本文のコラム(p. 226)で紹介されている実行結果もそうなっています。これではもったいないので、for文ではなくwhile文を使うようにするといいでしょう。

45   jMax=10^i
50   while j<jMax
60   x=rnd:y=rnd:if x*x+y*y<=1 then k=k+1
65   j=j+1
70   wend

問題というか要望(プログラミング言語の選択)

1993年に初版が出版されて以来、本書は売れ続け、つまり生き続けているわけですが、利用されている言語UBASICのほうは瀕死の状態、64bit版Windows 7では動きません(参考:UBASIC を Windows Vista/7 で動かす方法)。ですから、長生きしそうなプログラミング言語を採用し、改訂していただきたいものです。大きな桁の整数のサポートが必要です。C言語+GMPなら寿命は長そうですがハードルが高いので、サポートが組み込まれているJava, Ruby, Python, Lispなどがいいでしょう。Javaに翻訳すると、下のようになります(まず紹介すべきはBigIntegerではなくintを使うバージョンですが、ここでは省略します)。

import java.util.*;
import java.math.*;

public class MonteCarlo {

  public static void main(String[] args) {
    BigInteger one = new BigInteger("1");

    System.out.print("10^n=");
    int n = new Scanner(System.in).nextInt();

    for (int i = 1; i <= n; i++) {
      BigInteger jMax = new BigInteger("10").pow(i);
      BigInteger k = new BigInteger("0");
      for (BigInteger j = k; j.compareTo(jMax) < 0; j = j.add(one)) {
        double x = Math.random();
        double y = Math.random();
        if (x * x + y * y <= 1) {
          k = k.add(one);
        }
      }
      System.out.printf("%s 点中 %s 個命中 Pi= %f\n",
          jMax, k, 4. * k.doubleValue() / jMax.doubleValue());
    }
  }
}

Javaで書くとなんかごちゃごちゃしていやですね。言語仕様が安定していない印象が強いRubyとPythonは採用しにくいので、残るのはLispですか。それなら、『素数夜曲 女王陛下のLISP』がちょうどいいですね(モンテカルロ法も扱われているようですし)。