本腰入れてActiveObjects (その1) EntityManager#migrate()

java-ja忘年会に急遽行ってみたらid:yuripopのサンタコスにヤラレタ。なんだかリア・ディゾンぽかったんだけど、id:yuripopってそういう方でしたっけ?ともかく、普通にかわいくてビビった。id:t_yano氏が終始ドキドキしていたのも頷ける。
そういえばActiveObjectsのEntityManager#migrate()がすごいっていう事を語れとid:yuripopから言われていた気がするので、今日はその辺を頑張ってみることにした。ちなみにActiveObjectsはJAMCircleの中でも利用しています。かれこれ半年くらいお付き合いしてきた成果があるのでつらつらと書いていきますかね。
さて、ActiveObjectsはインターフェースベースのO/Rマッパーです。RailsのActiveRecodeに影響されて作られました。と言っても、他のO/Rマッパーは頭の片隅にも残ってないので、O/Rマッパーって普通インターフェースベースでしょ?ってツッコミを頂くかもしれませんね。でも基本的にDBを使わないようにしてたので、よく知りません。3年くらい前にHibernateを使った事ありますけど、設定ファイルにいろんな事を書かなきゃいけなくて、めんどかったです。ActiveObjectsは基本的に設定ファイルにガシガシテーブルとオブジェクトのリレーションを書いていくなんて事はありません。
インターフェースにアノテーションで色々書いていく事でオブジェクト同士を関連付けたり、DBに格納するタイプを指定したりします。DBのテーブル名やカラム名は規約を使って自動的にマッピングされるので、ほとんど意識しません。自動的にマッピングするためのDBスキーマを作ってくれるのが、EntityManager#migrate(RawEntity...)です。
RawEntityはDBの1行に対するインターフェースで、それを継承して永続化したいオブジェクトのインターフェースを作成していきます。Genericsで指定しているTはPKの型を指定します。既存のものでEntityという、PKのカラム名をID、型をIntegerとしているインターフェースがあるので、普通はそれを継承すればいいでしょう。こんな感じで作ります。

	public interface Person extends Entity{
		public String getName();
		public void setName(String name);
		public boolean isClever();
		public void setClever(boolean clever);
	}

こうすると、Personという名前のテーブルと、文字列型のnameカラム、論理値型のcleverカラムが作成されるわけです。ここで出てきた型は、DBごとに適した型で作成されます。その辺はまた今度。
話をEntityManger#migrate()に戻します。このメソッドを呼び出すと、指定したインターフェースのテーブルが無いか検索し、無ければ新規作成ということでCREATE TABLE文を、あれば、既存のテーブルと比較し、たらない項目があればALTER TABLE文で追加、多すぎる項目があればDROP TABLE文→CREATE TABLE文を実行します。なのでオブジェクトのプロパティを追加していくだけであれば基本的にデータ移行は発生しません。っていうのがEntityManager#migrate()のすごいところ。さっきの例で言うと、整数型のageカラムを追加しよう、ってなったときは

	public interface Person extends Entity{
		public String getName();
		public void setName(String name);
		public boolean isClever();
		public void setClever(boolean clever);
		// 追加
		public int getAge();
		// 追加
		public void setAge(int age);
	}

ってした後で、

// DBごとの接続先Provider
DatabaseProvider provider = new なんとかProvider(uri,username,password);
EntityManager manager = new EntityManager(provider);
manager.migrate(Person.class);

とかしてあげると、それだけでPersonテーブルが更新されます。
今のActiveObjectsではサポートされていませんが、将来的には新規に追加したカラムの移行直後の値をアノテーションで指定できるようにするらしい。

12/24追記
ちなみにEnumもDBに自動的にマッピングしてくれます。例えば先ほどのPersonの例でいうと、

public enum Gender implements Serializable{
  MALE,FEMALE
}
public interface Person extends Entity{
  ... 中略 ...
  public Gender getGender();
  public void setGender(Gender gender);
}

とかすると、DBに整数型のGENDERカラムができて、MALEだったら0、FEMALEだったら1を格納してくれます。もちろん取り出す時は意識しないよ。この例だとenumに他のタイプを追加しづらいです(ゲイとか?)けど、例えばJAMCircleの中だと色をenumとして定義しているので、新しい色をどんどん後ろに追加していく事ができますね。

次回はPreloadアノテーションと、Implementationアノテーションあたりについて語ってみます。