Scala から MongoDB へアクセスする Casbah を真面目に使ってみる
でScalaからMongoにアクセスする為のライブラリはインポートしてみたが、使ってなかったので、改めて使ってみる。
MongoDB のデータ構成
まずどんな階層構造でデータを保持してるのかを確認する。
Getting Started with MongoDB — MongoDB Manual 2.4.8
この辺を確認してみると、MongoDB のデータ厚生は
- MongoDB collection key -> value ... collection *** ...
てな構成らしい。
これだけ分かれば、DBとして使えなくはない。
Casbah 使ってみる
まずこのクライアント、チュートリアルは1. Tutorial: Using Casbah — Casbah (MongoDB + Scala Toolkit) Documentation 2.6.3 documentationに存在してる。
まずいきなり疑問。この MongoClient ってコネクションプールどうなってるのか?あと、ThreadSafeなのかどうかが問題。
まずクライアントプールについては StackOverflow に答えがあった。
scala - How to pool the connections of mongodb with casbah? - Stack Overflow
出展がほしかったが、、、、。
で、ThreadSafety については、Java 側がスレッドセーフだったのでおそらくスレッドセーフだ。Java Driver Concurrency
まぁ MongoClient がスレッドセーフではないとかそんな狂った話はなかろうさ。 後、データベースアクセサ(MongoDBインスタンス)はスレッド単位でキャッシュすればよさそうだ。そもそもTransactionが無いのだから、単一スレッド上で二つ以上の MongoDB インスタンスが必要とも思えない。
ので、Java の ThreadLocal クラスを利用して、キャッシュしよう。
package models.connections import com.mongodb.casbah.Imports._ trait DB { private val host = "127.0.0.1" private val port = 27017 private val databaseName = "rss_reader" private val client = MongoClient(host, port) private val threadLocal = new ThreadLocal[MongoDB] def db(dbName:String):MongoDB = client(dbName) def get() = { Option(threadLocal.get()).getOrElse({ val dbInstance = db(databaseName) threadLocal.set(dbInstance) dbInstance }) } } object DB extends DB
マジックナンバーは設定ファイルにでも書き出せばOKかなと。
しかし、、、、この公式の import やら書き方やらの説明の無さは異常で、実行した際の型説明すらないとストレスがマッハな訳ですが、、、。
突っ込んだデータをMongo内の型だけで一般型のキャストまで書かれてなくてこれでどうやってプログラムを書けというのか...
そっちがその気なら全部調べるしかねーよなぁ!
- insert
object Sample extends App { val col = DB.get().getCollection("test_collection") val inserts = MongoDBObject("uid" -> "SomethingId", "provider" -> "FACEBOOK") val writeRes = col.insert(inserts) println("toString : " + writeRes) println("Errors : " + writeRes.getError) println("N : " + writeRes.getN) } /* toString : { "serverUsed" : "/127.0.0.1:27017" , "n" : 0 , "connectionId" : 14 , "err" : null , "ok" : 1.0} Errors : null N : 0 */
- findOne
これが独自型で帰ってきて何をどうとればいいのか意味不明である。
が、どうも toMap するとキーが文字列、中身が Map[Any, Any] で返るっぽい。てか Java のしかもGenericsですらない Map で返ってくるよ。何だこのScalaっぽくない仕様は、、、
object Sample extends App { val col = DB.get().getCollection("test_collection") val inserts = MongoDBObject("uid" -> "SomethingId", "provider" -> "FACEBOOK") val res = col.findOne(inserts) println("res : " + res) println("keys : " + res.keySet()) println("map : " + res.toMap.getClass) }
入れたのがStringしか入れていないし、そのままキャストできるかなーとStringにキャストするとエラー
[error] (run-main) java.lang.ClassCastException: org.bson.types.ObjectId cannot be cast to java.lang.String java.lang.ClassCastException: org.bson.types.ObjectId cannot be cast to java.lang.String at models.connections.Sample$$anonfun$1.apply(Sample.scala:34) at models.connections.Sample$$anonfun$1.apply(Sample.scala:32) at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244) at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244) at scala.collection.Iterator$class.foreach(Iterator.scala:727) at scala.collection.AbstractIterator.foreach(Iterator.scala:1157) at scala.collection.IterableLike$class.foreach(IterableLike.scala:72)
何なんだBSONって... もしかして BSON 以外のオブジェクト型で返ってきたりとかするのか!? と思ってBSON調べてみると、
マジかこれ、、、バイナリJSONでつか、、、、すなわち取得できるのはバイナリであると、、、。
と思いきや、ダメ元でフィールドをひとつづつ get すると
val inserts = MongoDBObject("uid" -> "SomethingId", "provider" -> "FACEBOOK") val res = col.findOne(inserts) println("map : " + res.get("uid").getClass) // map : class java.lang.String
- update
val col = DB.get().getCollection("test_collection") val tgt = MongoDBObject("uid" -> "SomethingId") val update = MongoDBObject("uid" -> "SomethingId", "provider" -> "FACEBOOK", "age" -> 64) val res = col.update(tgt, update) println(res)
insert では更新できない。KVSとも違うからある種そういうものなのかも?
で、整数を突っ込んだ後で findOne して get するとどーなるかというと
val tgt = MongoDBObject("uid" -> "SomethingId") val res = col.findOne(tgt) println("res : " + res) println("keys : " + res.keySet()) println("map : " + res.get("age").getClass)
結果
res : { "_id" : { "$oid" : "5292e7410cf249da779c4a85"} , "uid" : "SomethingId" , "provider" : "FACEBOOK" , "age" : 32} keys : [_id, uid, provider, age] map : class java.lang.Integer
一応は拾って来れるのね、、、。
- find で全件取得
DBCursor 型で返るらしい。java.lang.Iterable
iterator メソッド実行して、JavaConversions をインポートすれば foreach 出来る。
val res = col.find() import scala.collection.JavaConversions._ res.iterator().foreach(value => { println("===========================") println("res : " + value) println("keys : " + value.keySet()) println("map : " + value.get("age").getClass) })
で、実行すると
=========================== res : { "_id" : { "$oid" : "5292ddf30cf275afbf800915"} , "uid" : "SomethingId" , "provider" : "FACEBOOK" , "age" : 64} keys : [_id, uid, provider, age] map : class java.lang.Integer =========================== res : { "_id" : { "$oid" : "5292e9700cf2f58bc894e697"} , "uid" : "Undefined" , "provider" : "TWITTER" , "age" : 64} keys : [_id, uid, provider, age] map : class java.lang.Integer
これはなんというか驚く程使いづらい。
Java のSQLドライバでももう少しましな IF してたと思うんだけどなぁ。
ちなみにまともに ObjectMap したければ サードパーティ使えと言ってる。