Scala で Morphia を使った MongoDB 操作

MongoDB のための Java 用オブジェクトマッピングツール MorphiaScala で使ってみました。(Morphia は Ruby の Mongoid や MongoMapper と同等のツール)

使用した環境は以下の通りです。

  • Scala 2.9.0
  • sbt 0.7.5
  • Morphia 1.0 Snapshot
  • MongoDB 1.9.0

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20110515/

sbt プロジェクトファイルの作成

sbt で Morphia を使用するための設定は以下のようになります。

project/build/MorphiaSample.scala
import sbt._

class MorphiaSample(info: ProjectInfo) extends DefaultProject(info) {
    //Morphia のリポジトリ設定
    lazy val morphiaSnapshot = "Morphia Repo at Google Code" at "http://morphia.googlecode.com/svn/mavenrepo"
    //Morphia を使用するための設定
    lazy val morphia = "com.google.code.morphia" % "morphia" % "1.00-SNAPSHOT"

    override def mainClass = Some("fits.sample.Sample")
}

モデルクラスの作成

Morphia では以下のようなアノテーションを使ってモデルクラスを定義します。

  • @Entity で永続化対象の指定
  • @Embedded で組み込みの指定
  • @Reference で参照の指定
  • @Id で ID の指定

また、モデルクラスには以下のような注意点があります。

  • デフォルトコンストラクタが必要
  • デフォルトではクラス名がコレクション名になる(@Entity の value で変更可)
  • デフォルトではコレクションへの保存時に className フィールドにクラス名が設定される(@Entity の noClassnameStored=true で保存しないように変更可)

それでは、id:fits:20110306 や id:fits:20110409 と同じモデル構成を Morphia 用に Scala で実装してみます。

以下の設定を行い User・Book・Comment のモデルクラスを作成しました。(Comment は Book に組み込まれるので @Entity は不要)

  • @Entity の value を使ってコレクション名を指定
  • @Entity の noClassnameStored = true で永続化ドキュメントにクラス名が挿入される事を防止
src/main/scala/User.scala
package fits.sample

import com.google.code.morphia.annotations._
import org.bson.types.ObjectId

@Entity(value = "users", noClassnameStored = true)
class User(var name: String) {
    //デフォルトコンストラクタ
    def this() = this("")

    @Id var id: ObjectId = null
}
src/main/scala/Book.scala
package fits.sample

import java.util.{List, ArrayList}
import com.google.code.morphia.annotations._
import org.bson.types.ObjectId

@Entity(value = "books", noClassnameStored = true)
class Book(var title: String, var isbn: String) {
    //デフォルトコンストラクタ
    def this() = this("", "")

    @Id var id: ObjectId = null
    //組み込みの定義(java.util.ArrayList を使用)
    @Embedded var comments: List[Comment] = new ArrayList[Comment]()
}
src/main/scala/Comment.scala
package fits.sample

import java.util.Date
import com.google.code.morphia.annotations._

class Comment {
    def this(content: String, user: User) = {
        this()
        this.content = content
        this.user = user
    }

    var content: String = ""
    var createdDate: Date = new Date()
    //参照の定義
    @Reference var user: User = null

実行クラスの作成

今回はスタンドアロン用の実行クラスを作成しました。
new Morphia().createDatastore() で取得した DataStore を使ってドキュメントの操作(追加・更新・削除・検索)を行います。

なお、Scala の場合は save の実行時に型指定する必要があるみたいです。

src/main/scala/Sample.scala
package fits.sample

import scala.collection.JavaConversions._
import com.google.code.morphia.Morphia
import com.mongodb.Mongo

object Sample {

    def main(args: Array[String]) {
        //接続設定
        val mongo = new Mongo("localhost")
        val db = new Morphia().createDatastore(mongo, "book_review")

        //User保存
        val u1 = new User("user1")
        db.save[User](u1)

        //User保存
        val u2 = new User("tester1")
        db.save[User](u2)

        //Book保存
        val b1 = new Book("ドメイン駆動設計", "9784798121963")
        b1.comments.add(new Comment("test", u1))
        b1.comments.add(new Comment("test2", u2))
        //保存(Book 型を明示する必要あり)
        db.save[Book](b1)

        //User更新
        val tempU1 = db.get(classOf[User], u1.id)
        tempU1.name = "user1-updated"
        db.save[User](tempU1)

        //Commentを追加してBook更新
        val tempB1 = db.get(classOf[Book], b1.id)
        tempB1.comments.add(new Comment("aaaaaaaa", tempU1))
        db.save[Book](tempB1)

        //Book全件取得
        db.find(classOf[Book]).asList.foreach {b =>
            printf("book: id = %s, title = %s, isbn = %s\n", b.id, b.title, b.isbn)

            b.comments.foreach {c =>
                printf("  comment: %s, date = %s, user = %s\n", c.content, c.createdDate, c.user.name)
            }
        }
    }
}

動作確認

事前に MongoDB を起動しておきます。

MongoDB 起動
> mongod -dbpath db

sbt を使って実行します。

実行例
> sbt run
・・・
[info] Running fits.sample.Sample
・・・
book: id = 4dcf4c21c55ab7ea6762b8bf, title = ドメイン駆動設計, isbn = 9784798121963
  comment: test, date = Sun May 15 12:44:33 JST 2011, user = user1-updated
  comment: test2, date = Sun May 15 12:44:33 JST 2011, user = tester1
  comment: aaaaaaaa, date = Sun May 15 12:44:33 JST 2011, user = user1-updated
・・・

実行後、mongo コマンドを使って保存されたドキュメントの内容を確認してみると以下のようになりました。

mongo コマンドでの確認(books コレクションの内容)
morphia_sample> mongo
MongoDB shell version: 1.9.0
> use book_review
switched to db book_review
> db.books.find()
{ "_id" : ObjectId("4dcf449bfe76b7ea826f2ec7"), "title" : "ドメイン駆動設計", "isbn" : "9784798121963", "comments" : [
    {
        "content" : "test",
        "user" : {
                "$ref" : "users",
                "$id" : ObjectId("4dcf449bfe76b7ea806f2ec7")
        },
        "createdDate" : ISODate("2011-05-15T03:12:27.337Z")
    },
    {
        "content" : "test2",
        "user" : {
                "$ref" : "users",
                "$id" : ObjectId("4dcf449bfe76b7ea816f2ec7")
        },
        "createdDate" : ISODate("2011-05-15T03:12:27.337Z")
    },
    {
        "content" : "aaaaaaaa",
        "user" : {
                "$ref" : "users",
                "$id" : ObjectId("4dcf449bfe76b7ea806f2ec7")
        },
        "createdDate" : ISODate("2011-05-15T03:12:27.383Z")
    }
] }