MongoDBとmungbean

sessionに格納するkey-valueのように、保存する要素の変動が多いオブジェクトを簡単に突っ込めるKVSを探していて、MongoDBに行き着きました。が、標準のJava DriverのAPIがイマイチなので、"Third Party Frameworks and Libs"で紹介されているライブラリを使ってみることにしました。これらのライブラリは大きく3つのカテゴリ(POJO Mappers/Code Generation/Misc)が用意されていますが、スキーマレスという特性を活かすのであればPOJO Mappersの方が良いと考え*1mungbeanというライブラリを使ってみました。
取りあえずmongodとmongoシェルを使って、testデータベースのfooコレクションにあらかじめデータを登録しておきます。

> db.foo.find()
{ "_id" : ObjectId("4d8f3719822cd27d3519b47f"), "a" : 1 }

mungbeanのjarを見つけることができなかったので、githubからコードをダウンロードし、mavenでパッケージして使ってます。FooとBarというPOJOを用意して試してみました。まずはFooのみ保存するコードを実行します。

実行後、mongoシェルで確認すると、以下のように保存されていました。

> db.foo.find()
{ "_id" : ObjectId("4d8f3719822cd27d3519b47f"), "a" : 1 }
{ "_id" : ObjectId("4dc192b4f66fd9d355b5f19a"), "id" : 1, "created" : ISODate("2011-05-04T17:53:56.386Z"), "name" : "hoge", "bars" : null }

Fooのみ保存するようにしたのは、createBarListを呼び出している行のコメントアウトを外すと、以下のエラーとなる為です。FooのIdプロパティ(int)にBarを設定しようとしている、ということなのですが、そんなはずもなく。

java.lang.RuntimeException: java.lang.IllegalArgumentException: Can not set int field test.entity.Foo.id to test.entity.Bar
	at mungbean.pojo.FieldDefinition.get(FieldDefinition.java:46)
	at mungbean.pojo.PojoEncoder.encode(PojoEncoder.java:58)
	at mungbean.protocol.bson.BSONCoder.write(BSONCoder.java:52)
	at mungbean.protocol.bson.AbstractBSONArray.encode(AbstractBSONArray.java:57)
	at mungbean.protocol.bson.BSONCoder.write(BSONCoder.java:52)
	at mungbean.protocol.bson.AbstractBSONMap.encode(AbstractBSONMap.java:54)
	at mungbean.pojo.PojoEncoder.encode(PojoEncoder.java:60)
	at mungbean.protocol.bson.BSONCoder.write(BSONCoder.java:69)
	at mungbean.protocol.message.InsertRequest.<init>(InsertRequest.java:19)
	at mungbean.AbstractDBCollection$1.doExecuteWithErrorChecking(AbstractDBCollection.java:77)
	at mungbean.AbstractDBCollection$ErrorCheckingDBConversation.execute(AbstractDBCollection.java:214)
	at mungbean.SingleNodeDbOperationExecutor.execute(SingleNodeDbOperationExecutor.java:53)
	at mungbean.SingleNodeDbOperationExecutor.executeWrite(SingleNodeDbOperationExecutor.java:67)
	at mungbean.AbstractDBCollection.executeWrite(AbstractDBCollection.java:230)
	at mungbean.AbstractDBCollection.save(AbstractDBCollection.java:73)
	at test.MongoTest.testSaveFoo(MongoTest.java:23)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
	at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
	at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
	at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
	at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
	at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66)
	at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
	at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
	at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
	at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: java.lang.IllegalArgumentException: Can not set int field test.entity.Foo.id to test.entity.Bar
	at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
	at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
	at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(Unknown Source)
	at sun.reflect.UnsafeIntegerFieldAccessorImpl.getInt(Unknown Source)
	at sun.reflect.UnsafeIntegerFieldAccessorImpl.get(Unknown Source)
	at java.lang.reflect.Field.get(Unknown Source)
	at mungbean.pojo.FieldDefinition.get(FieldDefinition.java:44)
	... 35 more

コレクションを取得する際にFoo.classを指定しているが故なのか、embeddingにするには違うアプローチが必要なのかわかりませんが…。Barを別のコレクションに保存して、FooにはBarのObjectIdだけ持たせる方が良いのかな。

追記: 2011/05/05

FooのインスタンスにBarのインスタンスのリストを設定するとエラーになる件ですが、mungbeanでPOJOを扱うTestCaseを見ても、複数のPOJOを対象としているケースはありませんでした。
コードを調べてみたところ、PojoEncoder#write→PojoEncoder#encode→BSONMap#encode→BSONArray#write→BSONArray#encode→PojoEncoder#write→PojoEncoder#encode、の順序でcallされており、最後のPojoEncoder#encodeでFooのField情報を使ってBarから値を取得しようとして失敗してました。Barのインスタンスを見つけたらPojoEncoderを作ってencodeしてやればBSONに変換できそうですが、現状、encode時にpersistenceの起点となるPOJO以外を見つけてしまうとダメそうです。

*1:Webで標準API以外の方式を探してみる限りprotocol bufferやDSLを使ったCode Generationのアプローチが多いようです