Apache Commons DbUtils

Apache CommonsのDbUtilsJDBCの補助ライブラリで、よく書くテンプレを簡略化することができます。DbUtilsを使うことでConnectionのcloseし忘れを防止することができたりもします。自分はQMACloneのデータベース部分にDbUtilsを使っています。

準備

解説では以下のMySQLのテーブルとJavaのclassを使います。

CREATE TABLE person (
  id SERIAL PRIMARY KEY,
  name TEXT
);
public class Person {
  public long id;
  public String name;

  public long getId() { return id; }
  public void setId(long id) { this.id = id; }
  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
};

初期化

DbUtilsJDBCを扱うときはQueryRunnerを使用します。自分はApache CommonsのDBCPで作成した接続プールと一緒に使っています。

QueryRunner runner = new QueryRunner(databaseConnectionPool.getDataSoure());

INSERT文・UPDATE文・REPLACE文・DELETE文

INSERT文・UPDATE文・REPLACE文・DELETE文はQueryRunner#update()を使って実行します。update()は第一引数にPreparedStatementに渡すものと同じ書式のSQL文、第二匹数以降にSQL文の"?"に当てはめる値を順に指定していきます。

runner.update("INSERT person SET name = ?", "hoge");
runner.update("UPDATE person SET name = ? WHERE id = ?", "fuga", 1);
runner.update("DELETE person WHERE id = ?", 1);

update()の内部では

  1. Connectionの確立
  2. PreparedStatementへのパラメーター指定
  3. SQL文の実行
  4. Connectionのclose

が行われています。自分でこれらの動作を書く必要がない分、コードが簡潔になっています。また、接続のcloseし忘れの心配もありません。

バッチ処理

INSERT文・UPDATE文・REPLACE文・DELETE文はQueryRunner#batch()を使ってバッチ処理することができます。batch()には第一引数にupdate()と同様にSQL文、第二引数にはSQL文の"?"に当てはめる値を順に格納したObjectの配列、Object[]を渡します。

List<Object[]> params = new ArrayList<Object[]>();
params.add(new Object[]{"hoge"});
params.add(new Object[]{"fuga"});
runner.batch("INSERT person SET name = ?", params.toArray(new Object[0][]));

batch()の内部ではupdate()と同様に

  1. Connectionの確立
  2. PreparedStatementへのパラメーター指定
  3. バッチ追加
  4. バッチ実行
  5. Connectionのclose

が行われています。

SELECT文

SELECT文はQueryRunner#query()を使って実行します。ResultSetHandlerインスタンスを渡して、handle()メソッドに渡されてきたResultSetから値を取り出します。handle()はT型を返すことができるようになっており、ここで返した値がquery()の返り値になります。

Person person = runner.query("SELECT * FROM person WHERE id = ?", new ResultSetHandler<Person>() {
  @Override
  public Person handle(ResultSet rs) throws SQLException {
    if (rs.next()) {
      Person person = new Person();
      person.id = rs.getLong("id");
      person.name = rs.getString("name");
      return person;
    }
    return null;
  }
}, 1);
List<Person> people = runner.query("SELECT * FROM person", new ResultSetHandler<List<Person>>() {
  @Override
  public List<Person> handle(ResultSet rs) throws SQLException {
    List<Person> people = new ArrayList<>();
    while (rs.next()) {
      Person person = new Person();
      person.id = rs.getLong("id");
      person.name = rs.getString("name");
      people.add(person);
    }
    return people;
  }
});

query()の内部では

  1. Connectionの確立
  2. PreparedStatementへのパラメーター指定
  3. SQL文の実行
  4. ResultSetのclose
  5. Connectionのclose

が行われています。ResultSetHandlerを使用する場合、handle()には何も操作がされてない状態のResultSetが渡されます。

AbstractListHandler

query()の戻り値をListにしたい場合、AbstractListHandlerで一部のコードを省略できる場合があります。

List<Person> people = runner.query("SELECT * FROM person", new AbstractListHandler<Person>() {
  @Override
  protected Person handleRow(ResultSet rs) throws SQLException {
    Person person = new Person();
    person.id = rs.getLong("id");
    person.name = rs.getString("name");
    return person;
  }
});

AbstractListHandlerArrayListインスタンスを生成し、handleRow()の戻り値をadd()し、query()の返り値とします。先ほどよりもコードが簡潔になっています。個人的にはAbstractCollectionHandlerという形で、自分で戻り値となる型を指定できるようにして欲しかったのですが、戻り値の方のキャストが必要になる場合が多いので一長一短かもしれません。createArray()でListのインスタンスを選べるようにするくらいはして欲しかったです。

AbstractKeyedHandler

query()の戻り値をMapにしたい場合、AbstractKeyedHandlerで一部のコードを省略できる場合があります。

Map<Long, Person> people = runner.query("SELECT * FROM person",
    new AbstractKeyedHandler<Long, Person>() {
      @Override
      protected Person createRow(ResultSet rs) throws SQLException {
        Person person = new Person();
        person.id = rs.getLong("id");
        person.name = rs.getString("name");
        return person;
      }

      @Override
      protected Long createKey(ResultSet rs) throws SQLException {
        return rs.getLong("id");
      }
    });

AbstractListHandlerはprotectedメソッドのcreateMap()でMapのインスタンスを生成し、ResultSetの各行についてcreateKey()とcreateRow()でキーと値を取り出し、Mapにput()していきます。こちらはcreateMap()をオーバーライドすることでMapのインスタンスを選べるようになっています。

ColumnListHandler

ある列のListが欲しい場合はColumnListHandlerを使うこともできます。ただしquery()の戻り値がListとなってしまうため、使いどころが難しいです。

List<Object> people = runner.query("SELECT * FROM person", new ColumnListHandler(name));

ColumnListHandlerはコンストラクタで指定した列の値のListを返してくれます。引数を省略した場合は1列目の結果のListを返してくれるので、以下のような簡潔な書き方もできます。

List<Object> people = runner.query("SELECT name FROM person", new ColumnListHandler());

SQLのint(10)型がJavaのLong型で返されてしまうため、自分は以下のようにカスタマイズしたColumnListHandlerを使っています。

public class ColumnListHandler<T> extends AbstractListHandler<T> {
  private final Class<T> c;
  public ColumnListHandler(Class<T> c) { this.c = c; }
  protected T handleRow(ResultSet rs) throws SQLException {
    Object object = rs.getObject(1);
    if (c == object.getClass()) {
      return c.cast(object);
    } else if (c == Integer.class && object.getClass() == Long.class) {
      // SQL の int(10) 型を Java の Long 型で返すため、特別処理
      return c.cast((Integer) (int) (long) (Long) object);
    } else {
      throw new IllegalArgumentException(String.format("%s から %s にキャストできません", object
          .getClass().toString(), c.toString()));
    }
  }
}

ScalarHandler

ResultSetの初めの一行の特定の列の値が欲しい場合はScalarHandlerを使うことができます。

long count = (long) runner.query("SELECT COUNT(*) FROM person", new ScalarHandler());

ScalarHandlerはResultSetの初めの行の特定の列の値を返してくれます。自分は上のようにCOUNT()やMAX()の値を取り出す時に使っています。

BeanHandler・BeanListHandler

Beansに対応したアクセサメソッドが定義されているclassであれば、自分でResultSetHandlerを書かずに値を取り出すことができます。

Person person = runner.query("SELECT * FROM person WHERE ID = ?", new BeanHandler<>(
    Person.class), 1);
List<Person> porple = runner.query("SELECT * FROM person", new BeanListHandler<>(
    Person.class));

BeanHandler・BeanListHandlerはBeanProcessorを使ってResultSetを指定したクラスのオブジェクトに変換して取り出してくれます。楽です。ただし、アクセサを書かなければならないのだ若干億劫です。

まとめ

JDBCを使うとき、Apache CommonsのDbUtilsを使うと定型文を省略して書くことができ、コーディングの効率を上げることができます。

リンク

透過型HTTPプロキシ

透過型HTTPプロキシを使うとブラウザのプロキシ設定を使用しなくても自動的にHTTPプロキシを使うことができるそうなのでやってみた。IEの設定でプロキシを無効にして、Google ChromeのinspecterからHTTPアクセスのヘッダを確認したところ、自宅サーバーのsquidからのヘッダがあることを確認した。

squid

/etc/squid/squid.confを書き換えて透過型プロキシのオプションを追加する。
http_port 3128 transparent

iptables

iptablesを使って80番ポートに来たHTTPアクセスを3128に転送する
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3128
転送を有効にするために/etc/sysctl.confに以下の設定を追加する。
net.ipv4.ip_forward=1

リンク

Ubuntu Linux サーバーをルーターにする

ある日突然Fedora LinuxTomcatサーブレットが動かなくなってしまった。本来であれば原因を調べて復旧すべきなのだが、一刻も早くQMACloneを復旧させたかったため、Ubuntuに乗り換えることにした。以下はその時の備忘録である。

PPPoEを用いてネットに繋ぐ

Ubuntu Serverにはデフォルトで"pppoeconf"というコマンドがインストールされるので、これを引数なしで実行してPPPoEの設定を行う。このとき接続IDはドメイン付きで指定する。ODNの場合は"@odn.ne.jp"。

pppoeconf

NICを立ち上げる

LAN側のNICのせっていを行う。

auto eth1
iface eth1 inet static
	address 192.168.0.1
	netmask 255.255.255.0
	broadcast 192.168.0.255

DHCPサーバーを立ち上げる

"dhcp3-server"をインストール後、以下のファイルを書き換える。

/etc/dhcp/dhcpd.conf
subnet 192.168.0.0 netmask 255.255.255.0 {
       range 192.168.0.128 192.168.0.191;
       option routers 192.168.0.1;
       option subnet-mask 255.255.255.0;
       option broadcast-address 192.168.0.255;
       option domain-name-servers 192.168.0.1;
       default-lease-time 6000;
       max-lease-time 7200;
}

/etc/default/dhcp3-server

INTERFACES="eth1"

LANの内側からWANに繋がるようにする

iptablesIPマスカレードの設定をする。設定はwebmin経由が楽。これ以外に以下の設定を行う。

/etc/sysctl.conf
net.ipv4.ip_forward=1

Fedora Linuxサーバーをルーターにする

QMACloneが夜間に重くなる原因を調査しているのだが、ネットワークに詳しい人に聞いたところ、Corega製の安物ルーターのせいではないかとアドバイスを頂いた。そこで、YAMAHANTT-ME製のルーターに買い換えようと思ったのだが、近くのPCショップには置いていなかった。仕方なく、LinuxサーバーにNICを2枚差してルーターにすることにした。設定方法を備忘録として書いておく。

Linuxサーバーに追加した設定は以下の通り。

  • PPPoEを用いてネットに繋ぐ
  • 新しく差したNICにstaticなIPを割り当てる
  • DHCPサーバーを立ち上げる
  • LANの内側からWANに繋がるようにする

PPPoEを用いてネットに繋ぐ

"rp-pppoe"というパッケージをインストールすると"pppoe-*"というコマンドが使えるようになる。このうち"pppoe-setup"というコマンドで、PPPoEの設定ができる。"pppoe-start"でPPPoE接続を開始、"pppoe-stop"で切断。
WAN側のNICの設定ファイル"/etc/sysconfig/network-scripts/ifcfg-*"は以下のようにしておくと、起動時にDHCPクライアントがタイムアウトするのを待たずに済むようになるらしい。

DEVICE="(インターフェース名)"
ONBOOT="yes"

新しく差したNICを立ち上げる

Fedora15からはNICのインターフェース名の命名規則が変わったため、若干分かりにくい。"ifconfig -a"でインタフェース名が確認できるのでメモ。"/etc/sysconfig/network-scripts/ifcfg-*"を以下のように設定する。

DEVICE="(インターフェース名)"
ONBOOT="yes"
BOOTPROTO="static"
TYPE="Ethernet"
IPADDR="192.168.0.1"
NETMASK="255.255.255.0"
NETADDR="192.168.0.0"
BROADCAST="192.168.0.255"
PEERDNS="no"

"sudo /etc/init.d/network restart"して設定を反映させる。

DHCPサーバーを立ち上げる

DHCPサーバーをインストールする。

$ sudo yum install dhcp

設定ファイル"sudoedit /etc/dhcp/dhcpd.conf"の内容は以下の通り。

subnet 192.168.0.0 netmask 255.255.255.0 {
       option routers      192.168.0.1;
       option subnet-mask  255.255.255.0;
       option domain-name-servers       143.90.130.39, 143.90.130.165, 8.8.8.8, 8.8.4.4;
       range  dynamic-bootp     192.168.0.128   192.168.0.191;
       default-lease-time       21600;
       max-lease-time           43200;
}

DNSサーバーを立ち上げるのが面倒だったため、ISPDNSサーバーを直書きしてしまった。後半二つはGoogleが提供しているDNS
起動時に起動するようにする。

$ sudo systemctl enable dhcpd.service

LANの内側からWANに繋がるようにする

iptablesIPマスカレードを行う。設定はwebmin経由が楽。以下のコマンドを打っておく。

echo "1" > /proc/sys/net/ipv4/ip_forward

127.0.0.1にアクセスできなくなった場合はiptablesを再起動してみる。

参考リンク

MySQLをバージョンアップした際にプロシージャーが動かなくなる問題

MySQLのメジャーバージョンを変更すると、プロシージャーが動かなくなることがある。これはバージョンアップによりプロシージャー関連のテーブルの構造が変更されたのが原因だそうだ。以下のコマンドを使うと一応動くようになる。

mysql_upgrade -uroot -p(管理者パスワード) --force

リンク

COBOLを全く知らない自分がCOBOLを想像してみた

自分の周りにはCOBOLを嫌う(性格にはCOBOLerを嫌う)人が多いように思う。自分はCOBOを知らないので、なぜそんなにCOBOLが嫌われているのか良く分からない。ということで、ネットで見つかる断片的な情報を元にCOBOLがどんな言語なのか想像してみた。

Wikipediaを中心にCOBOLに関する断片的な情報を集めてみると、こんな感じになった。

  • 自然言語である英語に近い記述になるようなコマンド語彙や構文
    • 全て現在形、語尾変化なし
    • ピリオド(厳密にはピリオド及びその後に続くスペース)を記述してコードの行端を示す
  • オブジェクト指向にも対応
  • カラムの位置が重要
  • 金額計算など事務処理(商用計算)の用途に広く使われている

まずは想像してみる

金額計算や事務処理ができるので、数値や文字列を扱うことが出来るはず。数値は多分整数と浮動小数だと思う。文字列はよく分からないが内部的にはUnicodeなんじゃないかな。ということで、数値や文字の表現はこんな感じ。

12,345,678,901,234,567,890
1,357,924,680.135792468
"コボル"

金額を計算しなければならないので、多分JavaのBigIntegerやBigDecimalみたいなやつになるとおもう。あと、人間が見やすいように数値の途中に桁区切りの","が入っていると思う。

多分変数という概念はあるので、変数への代入はきっとこんな感じ。

ASSIGN 1 TO A.

四則演算もあるはずで、きっとこんな感じ

ADD 2 TO B.
SUBTRACT 3 FROM C.
MULTIPLY D BY 4.
DIVIDE E BY 5.

でもこれだけだと複雑な式の計算がものすごく面倒になるので、texの数式表現も使えると思う。

ASSIGN 6.0 TO X.
ASSIGN $\exp^{X}+\exp^{-X}$ TO F.

画面に表示するときは、きっと帳簿とか領収書みたいなやつしか表示しないはずなので、宣言部分でHTMLの雛形を定義して、そこのにテンプレートエンジン的な感じで当てはめるのだと思う。あと、オブジェクト指向だから、何とかのオブジェクトの中の何とかという表現が使えるはず。Pythonの"""とかも使えるんじゃないかな。

ASSIGN """<html>
<header><title>タイトル</title></heaer>
<table><tr><td>$Y$</td><td>$Z$</td></tr></table>
</html>""" TO GAMEN AS TEMPLATE.
ASSIGN "左" TO GAMEN'S Y.
ASSIGN "右" TO GAMEN'S Z.

標準出力くらい使えるよね。

WRITE GAMEN TO STANDARD OUTPUT.

条件分岐も必要のはず。カラムの位置が重要と書いてあったので、多分Pythonみたいにインデントでブロックを分ける方式になっているんじゃないかな。インデントはタブだと視覚的に見づらいので、先頭の"-"のかずで行うことにする。使われている予約後はBASICのやつっぽい気がするので、

IF G IS 7 THEN
-ASSIGN 10 TO H.
-MULTIPLY G BY 8.
ELSE
-ASSIGN 11 TO H.
-MULTIPLY G BY 9.
-IF G IS 42 THEN
--CALCULATE $\log(X)$ AND ASSIGN TO H.

ループもあると思う。きっとネスト出来る

ASSIGN 1 TO I.
REPEAT UNTIL I IS LESS THAN 10
-ASSIGN 1 TO J.
-REPEAT UNTIL J IS LESS THAN 10
--WRITE I TO STANDARD OUTPUT.
--WRITE "×" TO STANDARD OUTPUT.
--WRITE J TO STANDARD OUTPUT.
--WRITE "=" TO STANDARD OUTPUT.
--CALCULATE $I*J$ AND ASSIGN TO K.
--WRITE K TO STANDARD OUTPUT.
--WRITE END OF LINE TO STANDARD OUTPUT.
--ADD 1 TO J.
-ADD 1 TO I.

きっとMaximaみたいに数式処理もお手のものなんじゃないかな。方程式解いたりするのとか。

ASSIGN 10.0 TO A.
ASSIGN 20.0 TO B.
ASSIGN 30.0 TO C.
SOLVE $AX^2+BX+C=0$ ABOUT X.
WRITE X TO STANDARD OUTPUT.

データベースも扱えないとまずい。多分こんな感じ?SQLとか使うのかなぁ?

DEFINE DATABASE CONNECTION AS CON WHERE ID IS "hoge" AND PASSWORD IS "fuga".
DEFINE DATABASE TABLE AS TBL WHERE TABLE NAME IS "hage" USING CON.
ASSIGN TBL'S "012345"'S "name" TO NAME.
WRITE NAME TO STANDARD OUTPUT.

多分こんな感じ。正直使いたくない。

注意

上記の架空言語はもちろん架空言語です。あまり面白くないですがエイプリルフールのネタとしてお赦しください。

Trac 0.12以降でsubversionからチケットを更新する方法

Trac 0.12以降を導入してsubversionのコミットからチケットをcloseできなくなってしまった。Trac 0.11まではsubversionのレポジトリのhookディレクトリにpost-commitを配置して、trac-post-commit-hookを呼び出せばよかったのだが、Trac 0.12からはやり方が変わったようだ。

Trac 0.12以降でsubversion経由でチケットをclose出来るようにするには以下ので順を踏めば良い。以下ではMTracで複数のプロジェクトを動かしている場合を想定している。ディレクトリ名等は適宜読み替えること。

手順

  • subversionのレポジトリのhookディレクトリに以下をpost-commitという名前で実行可能属性をつけて配置。
#!/bin/sh
export TRAC_ADMIN="/opt/local/bin/trac-admin"
export TRAC_ENV_PARENT_DIR="/Volumes/Data/trac"
export PYTHON_EGG_CACHE="${TRAC_ENV_PARENT_DIR}/.egg_cache"
export TRAC_ENV="${TRAC_ENV_PARENT_DIR}/hoge"
${TRAC_ADMIN} ${TRAC_ENV} changeset added "$1" "$2"
  • 同様にpost-revprop-changeを配置
#!/bin/sh
export TRAC_ADMIN="/opt/local/bin/trac-admin"
export TRAC_ENV_PARENT_DIR="/Volumes/Data/trac"
export PYTHON_EGG_CACHE="${TRAC_ENV_PARENT_DIR}/.egg_cache"
export TRAC_ENV="${TRAC_ENV_PARENT_DIR}/hoge"
${TRAC_ADMIN} ${TRAC_ENV} changeset modified "$1" "$2"
  • trac-adminを使用してユーザーに管理画面の使用権限を付加
$ trac-admin hoge permission add fuga TRAC_ADMIN
  • tracのページの右上にある管理->プラグインからCommitTicketUpdaterを有効にする。

以上で0.11以前と同様に"close #123"や"fix #234"と書くだけでチケットがcloseされるようになった。

前の方法とこの方法とどちらがエレガントなのだろう・・・。

リンク