ScalaからMongoDBへアクセスする - Casbah編

March 7, 2011 - Scala

ここ2,3日、ScalaからMongoDBへのアクセスのため、CasbahとSalatをお試し中です。

もともとはAkkaを弄ってたんですが、Akkaに含まれるPersistent(MongoDBなどNoSQLへの接続用)がなくなるらしいので、別のがないかなー、と寄り道したのがきっかけです。

Casbah

MongoDBを開発している10gen謹製Scalaライブラリです。 Scala製のドライバは他にもLiftのMongoDBや、Liftと組み合わせて使うRogue、mongo-java-driverのラッパーのmongo-scala-driverなどがあります。 一覧は以下に掲載されています。

Java Language Center - MongoDB

Rogueについては、Fungoingのbibrostさんのブログが詳しいのでそちらをご覧ください。

Fungoing Labs: Lift用Scala DSL”Rogue”を使ってMongoDBにアクセス(1)〜概要編〜

なぜ、LiftのMongoDBなりRogueなりを使わなかったかというと、フレームワークに依存したくなかったからです。単体で動作するものが使いたいのです。なので、Casbahを選択しました。 が、Casbahだけだと、複雑なレコードを作成するのには少々面倒なところがあります。ORマッパーが欲しくなるのです。その解決策となるSalatがあります。今回はCasbahのまとめにとどめ、Salatについては後日改めてまとめようと思います。

※追記※ Salat編を書きました。 ScalaからMongoDBへアクセスする ? Salat編
では、Casbahの使い方について見ていきます。

インストール

sbtの設定はこんな感じです。 project/build/SalatTestProject.scala

そしたら、sbtプロンプトでreload, updateをします。

これで、project/lib_managed/scala_2.x.x/compile以下にjarファイルが読み込まれるはず。

CasbahでMongoDBへアクセス

目次は以下の通りです。

  • MongoDBObjectの作成
  • ドキュメントの検索
  • ドキュメントのJOIN
  • ListObjectの生成
  • クエリの構築
  • クエリの構築(DSLを利用)

MongoDBObjectの作成

Casbahでは、MongoDB上のドキュメントをMongoDBObjectとして取り扱います。

MongoDBObjectをインスタンス化する、もしくはMongoDBObjectBuilderで逐次フィールドを定義するかのどちらかになります。

// MongoDBObjectをインスタンス化
val user = MongoDBObject("id"->1, "name"->"me")
println(user) // { "id" : 1 , "name" : "me"}

// MongoDBObjectBuilderで逐次フィールド定義
val builder = MongoDBObject.newBuilder
builder += "id"->2
builder += "name"->"you"
val user2 = builder.result // これでMongoDBObjectができる

できたら、これをMongoDBに保存します。

まずはコレクションのインスタンス取得から。

val conn = MongoConnection()
// MongoConnection("localhost", 27017)でホストおよびポート指定が可能
val db = conn("casbah_test")
val collection = db("sample")

下記のように1行にまとめてもでもOKです。

val collection = MongoConnection()("casbah_test")("sample")

で、コレクションに保存します。「+=」を使うだけと、とてもシンプルな操作になっています。

collection += user // { "_id" : ObjectId("4d7385ebf21423dcecb4c578"), "id" : 1, "name" : "me" }
collection += user2 // { "_id" : ObjectId("4d7385ebf21423dcedb4c578"), "id" : 2, "name" : "you" }

ドキュメントの検索

保存したドキュメントを全件取得します。mongodbのコンソールでいう、db.collection.find()にあたる操作です。

collection.find().foreach { println(_) }

ドキュメントのJOIN

MongoDBでJoinはできないので、アプリ側で対処する場合に使うんでしょうかね。

やり方は、MongoDBObjectを「++」でつなぐだけ。簡単です。

val identity = MongoDBObject("name"->"me", "age"->27)
val address = MongoDBObject("country"->"Japan", "prefecture"->"Tokyo")
val user = identity ++ address
println(user) // { "age" : 27 , "country" : "Japan" , "prefecture" : "Tokyo" , "name" : "me"}

ListObjectの生成

List(javascriptでいうArrayのこと)はMongoDBListでつくります。

val users1 = MongoDBList(
  MongoDBObject("name"->"me"),
  MongoDBObject("name"->"you"))
println(users1) // [ { "name" : "me"} , { "name" : "you"}]

当然ですが、Listの要素(MongoDBListの引数は)MongoDBObjectだけでなく、文字列や数値もOKです。

でListの場合もBuilder経由で作成が可能です。

val users = MongoDBList.newBuilder
val user1 = MongoDBObject("name"->"me")
val user2 = MongoDBObject("name"->"you")
users += user1
users += user2
println(users.result) // [ { "name" : "me"} , { "name" : "you"}]

クエリの構築

// 取得条件を指定
val condition = MongoDBObject("name"->"me")
collection.find(condition).foreach( println )

// 取得フィールドを限定
val fields = MongoDBObject("id"->1)
collection.find(condition, fields).foreach( println )

// 条件の指定をせずに取得フィールドのみ限定する場合
val conditionEmpty = MongoDBObject.empty
collection.find(conditionEmpty, fields).foreach( println )

クエリの構築(DSLを利用)

$exists, $gt, $ltなど、以下にあるものはDSLとして使えるらしいです。

findの引数にDSLを使って記述します。複数条件を組み合わせる場合は「++」でつなげます。

collection.find( "name" $exists true ).foreach{ println }

// 組み合わせる場合は"++"でつなげる
collection.find( ("name" $exists true) ++ ("age" $gte 20 $lt 30 ) ).foreach{ println }

『(“age” $gte 20) ++ (“age” $lt 30)』と書かなくても、『(“age” $gte 20 $lt 30 ) 』とかけるので記述量がへって良いですね。

あと、追加ですけど、1ドキュメントのみ取得したい時はfindOneが使えます。

val obj = collection.findOne().get // Option[DBObject]からDBObjectを取り出すためにgetを呼ぶ
println(obj("name")) //me

以上、かんたんにSasbahの使い方についてまとめてみました。

関連リンク

ちなみに、細切れで説明した上記サンプルプログラムの全体を以下に掲載しておきます。

※convertObjectTypeメソッドの中身については現在検証中です(;・∀・)