CLOVER🍀

That was when it all began.

ClojureでMongoDBクライアントプログラミング

以前、JavaVM系の言語、Java、Groovy、Scalaを使って簡単なMongoDBクライアントプログラミングをまとめました。

Java/Groovy/ScalaでMongoDBクライアントプログラミング
http://d.hatena.ne.jp/Kazuhira/20130615/1371303505

Clojureも書いてみたので、別エントリではありますが記載しておこうと思います。

で、Clojure用のドライバですが

Clojure
See the Java Language Center

http://docs.mongodb.org/ecosystem/drivers/community-supported-drivers/

Java用のを使え!以上!

…左様でございますか。というわけで、まずは普通にJava Driverを使ってみようと思います。この時点でのバージョンは2.11.2です。

Java Driver
http://docs.mongodb.org/ecosystem/drivers/java/

Getting Started with Java Driver
http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-java-driver/#getting-started-with-java-driver

API Documentation
http://api.mongodb.org/java/2.11.2/

ほぼ、前のエントリをClojureに移植しただけです。
getting_starged.clj

(use '[leiningen.exec :only (deps)])
(deps '[[org.mongodb/mongo-java-driver "2.11.2"]])

(ns mongodb.getting-started
  (:import (com.mongodb MongoClient WriteConcern BasicDBObject)))

;; MongoDBに
(with-open [client (MongoClient. "localhost" 27017)]
  (let
      ;; 使用するデータベース
      [db (.getDB client "tutorial")
       ;; コレクション名を「nosql」にします
       nosql (.getCollection db "nosql")]

    ;; データの登録
    ;; 1件ずつですが、このAPIだとメソッドが推測できないっぽい?
    (doto nosql
      (.insert (into-array [(-> (BasicDBObject. "name" "MongoDB")
                                (.append "type" "Document Database"))]))
      (.insert (into-array [(-> (BasicDBObject. "name" "CouchDB")
                                (.append "type" "Document Database"))]))
      (.insert (into-array [(-> (BasicDBObject. "name" "Memcached")
                                (.append "type" "Key Value Store"))])))

    ;; 複数件
    (let [documents (list (-> (BasicDBObject. "name" "Apache Cassandra")
                              (.append "type" "Column Database"))
                          (-> (BasicDBObject. "name" "Apache HBase")
                              (.append "type" "Column Database")))]
      (.insert nosql documents))

    ;; 検索
    (dorun
     (for [object (.find nosql)]
       (printf "find => %s%n" object)))
    (dorun
     (for [object (.find nosql (BasicDBObject. "type" "Column Database"))]
       (printf "find(conditioin) => %s%n" object)))

    ;; findOne
    (printf "findOne => %s%n" (.findOne nosql))
    (printf "findOne(condition) => %s%n"
            (.findOne nosql (BasicDBObject. "name" "Apache Cassandra")))

    ;; 件数
    (printf "count => %d%n" (.count nosql))
    (printf "count(condition) => %d%n"
            (.count nosql (-> (BasicDBObject. "name" "Apache Cassandra")
                              (.append "type" "Column Database"))))

    ;; 更新
    ;; $set
    (.update nosql
             (BasicDBObject. "name" "MongoDB")
             (BasicDBObject. "$set" (BasicDBObject. "url" "http://www.mongodb.org/")))
    (.update nosql
             (BasicDBObject. "name" "Memcached")
             (BasicDBObject. "$unset" (BasicDBObject. "type" 1)))

    ;; save
    (.save nosql (-> (BasicDBObject. "name" "Redis")
                     (.append "type" "Key Value Store")))
    (let [redis (.findOne nosql (BasicDBObject. "name" "Redis"))]
      (.append redis "url" "http://redis.io/")
      (.save nosql redis))

    ;; ドキュメントの削除
    (.remove nosql (BasicDBObject. "name" "Apache HBase"))

    (dorun
     (for [object (.find nosql)]
       (printf "find => %s%n" object)))

    ;; コレクションの全データ削除
    (.remove nosql (BasicDBObject.))

    ))

Leiningenのlein-execプラグインを前提にしているので、実行は

$ lein exec getting_starged.clj

となります。

BasicDBObjectを作るのはやっぱり面倒なことと、ハマったのはコレクションに対するinsert。最初、

    (doto nosql
      (.insert (-> (BasicDBObject. "name" "MongoDB")
                   (.append "type" "Document Database"))))

みたいに書いていたのですが、これだと

java.lang.IllegalArgumentException: No matching method found: insert for class com.mongodb.DBApiLayer$MyCollection

とメソッドが解決できずにコケてくれるので、こういう形になりました。

    (doto nosql
      (.insert (into-array [(-> (BasicDBObject. "name" "MongoDB")
                                (.append "type" "Document Database"))]))
      (.insert (into-array [(-> (BasicDBObject. "name" "CouchDB")
                                (.append "type" "Document Database"))]))
      (.insert (into-array [(-> (BasicDBObject. "name" "Memcached")
                                (.append "type" "Key Value Store"))])))

わざわざ可変長引数に合うように、into-arrayで配列にしているという…。

それ以外は、特筆することはないかな?

Java Driver以外について

MongoDBのオフィシャルサイトには、Third Party製のライブラリとかは記載されていませんが、実際にはいくつか存在します。

CongoMongo
https://github.com/aboekhoff/congomongo

Monger
http://clojuremongodb.info/

Mongoika(日本人の方が作成)
https://github.com/yuushimizu/Mongoika

Karras
https://github.com/wilkes/karras

Hassium
https://github.com/weavejester/hassium

基本的には、どれもJava Driverをラップしたものです。

ちょっと調べた感じだと、CongoMongoが一般的な感じがするのですが、この中でClojureとMongoDBのJava Driverの最新に追従できているのはMongerだけだったので、今回はMongerを使ってみることにしました。

Monger

というわけで、Clojure用のMongoDB向けドライバ、Mongerを使ってみたいと思います。

Monger
http://clojuremongodb.info/

オフィシャルサイトに、こんな画像とか

こんな画像とか

貼られていて、ちょっと異様な雰囲気を出しています…。

基本的にはGetting Startedを見ながら進めていく感じですが、

Getting Started
http://clojuremongodb.info/articles/getting_started.html

必要に応じてドキュメントを参照しながら。

Guide
http://clojuremongodb.info/articles/guides.html

API Referenceもあります。

API Reference
http://reference.clojuremongodb.info/

では、実際に書いてみたコードを。
monger.clj

(use '[leiningen.exec :only (deps)])
(deps '[[com.novemberain/monger "1.5.0"]])

(ns monger.getting-started
  (:require [monger.core :as mc]
            [monger.collection :as mcol]
            [monger.operators :as mop]))

;;(mg/connect!)  ;; ローカルホストに、デフォルトポートで接続

(mc/connect! {:host "localhost" :port 27017}) ;; ホスト名とポートを明示

;; 使用するデータベース
(mc/set-db! (mc/get-db "tutorial"))

;; コレクションの名前は、各関数の引数に指定する

;; データの登録
(mcol/insert "nosql" {:name "MongoDB" :type "Document Database"})
(mcol/insert "nosql" {:name "CouchDB" :type "Document Database"})
(mcol/insert "nosql" {:name "Memcached" :type "Key Value Store"})

;; 複数件
(mcol/insert-batch "nosql" [{:name "Apache Cassandra" :type "Column Database"}
                            {:name "Apache HBase" :type "Column Database"}])

;; 検索
(dorun
 (for [object (mcol/find "nosql")]
   (printf "find => %s%n" object)))
(dorun
 (for [object (mcol/find "nosql" {:type "Column Database"})]
   (printf "find(condition) => %s%n" object)))

;; Clojureのマップとしても取得できます
(dorun
 (for [m (mcol/find-maps "nosql")]
   (printf "find-maps :name=%s :type=%s%n" (:name m) (:type m))))
(dorun
 (for [m (mcol/find-maps "nosql" {:type "Column Database"})]
   (printf "find-maps(condition) :name=%s :type=%s%n"
           (:name m)
           (:type m))))

;; find-one
(printf "find-one => %s%n" (mcol/find-one "nosql" {}))
(printf "find-one(condition) => %s%n"
        (mcol/find-one "nosql" {:name "Apache Cassandra"}))

;; find-oneも、マップとして戻せます
(printf "find-one-map => %s%n" (mcol/find-one-as-map "nosql" {}))
(printf "find-one-map(condition) => %s%n"
        (mcol/find-one-as-map "nosql" {:name "Apache Cassandra"}))

;; 件数
(printf "count => %d%n" (mcol/count "nosql"))
(printf "count(condition) => %s%n"
        (mcol/count "nosql" {:name "Apache Cassandra" :type "Column Database"}))

;; 更新
;; $set
(mcol/update "nosql" {:name "MongoDB"} {mop/$set {:url "http://www.mongodb.org/"}})
(mcol/update "nosql" {:name "Memcached"} {mop/$unset {:type 1}})

;; save
(mcol/save "nosql" {:name "Redis" :type "Key Value Store"})
(let [redis (mcol/find-one-as-map "nosql" {:name "Redis"})
      updated (assoc redis :url "http://redis.io/")]
  (mcol/save "nosql" updated))

;; ドキュメントの削除
(mcol/remove "nosql" {:name "Apache HBase"})

(dorun
 (for [obj (mcol/find "nosql")]
   (printf "find => %s%n" obj)))

;; コレクションの全データ削除
(mcol/remove "nosql")

(mc/disconnect!)

Getting Startedを見てたりするとすぐに気付きますが、基本的にはコネクションとか使用するDBとかはデフォルトを設定してしまって、あとはコレクション名を指定して実行していくスタンスみたいです。

API Referenceを見ていくと、一応コネクションやDBを引数指定できるものもあるようですが、ドキュメントではそんなに紹介されてませんし…。disconnect!って、デフォルトのコネクションを切るやつしかなくない??

まあ、Clojure向けだけあって、Clojureのマップ形式でMongoDBとやり取りできるというのは良いかなぁとは思いますが。

とりあえず、そんな感じで。