CLOVER🍀

That was when it all began.

Java/Groovy/ScalaでMongoDBクライアントプログラミング

前回はMongoDB Shellからでしたが、今度はJava系の言語からMongoDBを扱ってみたいと思います。

今回行うのは、前回の縮小版です。
http://d.hatena.ne.jp/Kazuhira/20130613/1371127093

はい。

で、対象はJava、Groovy、Scalaで。それではいってみましょう。

Clojure版も書きました
http://d.hatena.ne.jp/Kazuhira/20130901/1378019591

Java

Javaは、オフィシャルなドライバが提供されています。今回使ったバージョンは、2.11.1ですね。

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.1/

基本的には、Getting Startedを見ていきつつ、ちょっと困ったらAPI Documentを見る感じで。

ビルドはGradleでやりました。
build.gradle

apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'MongoDBClientSample'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.mongodb:mongo-java-driver:2.11.1'
}

作成したコード。
src/main/java/MongoDBClientSample.java

import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;

import com.mongodb.MongoClient;
import com.mongodb.MongoException;
import com.mongodb.WriteConcern;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.DBCursor;

public class MongoDBClientSample {
    public static void main(String[] args) {
        MongoClient mongoClient = null;

        try {
            // MongoDBに接続
            mongoClient = new MongoClient("localhost", 27017);
            // ローカルで動かすなら、以下でもOK
            // mongoClient = new MongoClient();
            // レプリカセットに接続する時は、ServerAddressを使うらしい…

            // 使用するデータベース
            DB db = mongoClient.getDB("tutorial");

            // コレクション名を「nosql」にします
            DBCollection nosql = db.getCollection("nosql");

            // データの登録
            // 1件ずつ
            nosql.insert(new BasicDBObject("name", "MongoDB")
                         .append("type", "Document Database"));
            nosql.insert(new BasicDBObject("name", "CouchDB")
                         .append("type", "Document Database"));
            nosql.insert(new BasicDBObject("name", "Memcached")
                         .append("type", "Key Value Store"));
            // 複数件
            List<DBObject> documents = new ArrayList<>();
            documents.add(new BasicDBObject("name", "Apache Cassandra")
                          .append("type", "Column Database"));
            documents.add(new BasicDBObject("name", "Apache HBase")
                          .append("type", "Column Database"));
            nosql.insert(documents);

            // 検索
            // find
            for (DBObject dbObject : nosql.find()) {
                System.out.println("find => " + dbObject);
            }
            for (DBObject dbObject : nosql.find(new BasicDBObject("type", "Column Database"))) {
                System.out.println("find(condition) => " + dbObject);
            }

            // findOne
            System.out.println("findOne => " +
                               nosql.findOne());
            System.out.println("findOne => " +
                               nosql.findOne(new BasicDBObject("name", "Apache Cassandra")));

            // 件数
            System.out.println("count => " +
                               nosql.count());
            System.out.println("count(condition) => " +
                               nosql.count(new BasicDBObject("name", "Apache Cassandra")
                                           .append("type", "Column Database")));

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

            // save
            nosql.save(new BasicDBObject("name", "Redis").append("type", "Key Value Store"));
            BasicDBObject redis = (BasicDBObject) nosql.findOne(new BasicDBObject("name", "Redis"));
            redis.append("url", "http://redis.io/");
            nosql.save(redis);

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

            for (DBObject dbObject : nosql.find()) {
                System.out.println(dbObject);
            }

            // コレクションの全データを削除
            nosql.remove(new BasicDBObject());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } finally {
            mongoClient.close();
        }
    }
}

基本的な意味はコメントに書いていますが、だいたいBasicDBObjectみたいなJSONライクなクラスを使いつつ、コレクションに対して操作していく感じみたいです。

かなり、面倒でした…。

Groovy

実は、最初にやったのはこちらです。いわゆるLLですしね、MongoDBとはGroovyの方がきっと相性が良いでしょう。

といっても、Groovyはオフィシャルなドライバは存在しません。サードパーティ製のGMongoというドライバがあります。

GMongo
https://github.com/poiati/gmongo

ちょっと使ってみると分かるのですが、これはJava Driverのラッパーな感じです。よって、Java DriverのAPI Documentとかもけっこう見ることになります。

それでは、作成したコードを。依存関係の解決には、Grapeを使用しています。
getting_started.groovy

@Grab('com.gmongo:gmongo:1.0')
import com.gmongo.GMongo

// MongoDBに接続
def mongo = new GMongo('localhost', 27017)
// ローカルで動かすなら、以下でもOK
// def mongo = new GMongo()

// 使用するデータベース
def db = mongo.getDB('tutorial')

// コレクション名を「nosql」にします

// データの登録
// 1件ずつ
db.nosql.insert([name: 'MongoDB', type: 'Document Database'])
db.nosql.insert(name: 'CouchDB', type: 'Document Database')
db.nosql << [name: 'Memcached', type: 'Key Value Store']
// 複数件
db.nosql << [
    [name: 'Apache Cassandra', type: 'Column Database'],
    [name: 'Apache HBase', type: 'Column Database']
]

// 検索
// find
db.nosql.find().each { println("find => $it") }
db.nosql.find(type: 'Column Database').each { println("find(condition) => $it") }

// findOne
println('findOne => ' +
            db.nosql.findOne())
println('findOne(condition) => ' +
            db.nosql.findOne(name: 'Apache Cassandra'))

// 件数
println('count => ' +
            db.nosql.count())
println('count(condition) => ' +
            db.nosql.count(name: 'Apache Cassandra', type: 'Column Database'))

// 更新
// $set
db.nosql.update([name: 'MongoDB'], [$set: [url: 'http://www.mongodb.org/']])
// $unset
db.nosql.update([name: 'Memcached'], [$unset: [type: 1]])

// save
db.nosql.save(name: 'Redis', type: 'Key Value Store')
db.nosql.findOne(name: 'Redis').with {
    db.nosql.save(it << [url: 'http://redis.io/'])
}

// ドキュメントの削除
db.nosql.remove(name: 'Apache HBase')

db.nosql.find().each { println(it) }

// コレクションの全データを削除
db.nosql.remove([:])

// 接続終了
mongo.close()

やっぱり、Groovyが1番簡単でした。Mapとかキーワード引数で簡単に操作できるのは、便利ですね。

Scala

Scalaの場合も、オフィシャルなドライバが存在します。Casbahという名前らしいです。

Casbah
http://mongodb.github.io/casbah/index.html

Tutorial: Using Casbah
http://mongodb.github.io/casbah/tutorial.html

API Documentation
http://mongodb.github.io/casbah/api/#com.mongodb.casbah.package

あと、こちらのサイトも参考にさせていただきました。
http://blog.restartr.com/2011/03/07/access-to-mongodb-in-scala-with-casbah/

今回使用したCasbahのバージョンは2.6.1ですが、Mavenリポジトリを見てると3.0.0が開発中のようですね。

ビルドには、sbtを使いました。
build.sbt

name := "mongodb-client-sample"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.10.2"

organization := "littlewings"

libraryDependencies += "org.mongodb" %% "casbah" % "2.6.1"

作成したコード。
src/main/scala/MongoDBClientSample.scala

import com.mongodb.casbah.Imports._

object MongoDBClientSample {
  def main(args: Array[String]): Unit = {
    // MongoDBに接続
    def mongo = MongoClient("localhost", 27017)
    // ローカルで動かすなら、以下でもOK
    // def mongo = MongoClient()

    try {
      // 使用するデータベース
      val db = mongo("tutorial")

      // コレクション名を「nosql」にします
      val nosql = db("nosql")

      // データの登録
      // 1件ずつ
      nosql.insert(MongoDBObject("name" -> "MongoDB",
                                 "type" -> "Document Database"))
      // += メソッドでもOK
      nosql += MongoDBObject("name" -> "CouchDB",
                             "type" -> "Document Database")
      // 実は、Mapでもinsertできます…
      /*
      nosql += Map("name" -> "CouchDB",
                   "type" -> "Document Database")
      */
      // Builderを使用
      val memcachedBuilder = MongoDBObject.newBuilder
      memcachedBuilder += "name" -> "Memcached"
      memcachedBuilder += "type" -> "Key Value Store"
      nosql += memcachedBuilder.result

      // 複数件
      nosql.insert(MongoDBObject("name" -> "Apache Cassandra",
                                 "type" -> "Column Database"),
                   MongoDBObject("name" -> "Apache HBase",
                                 "type" -> "Column Database"))

      // 検索
      // find
      nosql.find.foreach(doc => println(s"find => $doc"))
      nosql.find(MongoDBObject("type" -> "Column Database"))
        .foreach(doc => println(s"find(condition) => $doc"))

      // findOne
      // 戻り値はOption
      nosql.findOne.foreach(doc => println(s"findOne => $doc"))
      nosql.findOne(MongoDBObject("name" -> "Apache Cassandra"))
        .foreach(doc => println(s"findOne(condition) => $doc"))

      // 件数
      println("count => " + nosql.count((dbObj: DBObject) => true))
      println("count(condition) => " +
              nosql.count((dbObj: DBObject) => {
                dbObj("name") == "Apache Cassandra" &&
                dbObj("type") == "Column Database"
              }))
      // find.countした方がいい?
      // println("count => " + nosql.find.count)
      // println("count(condition) => " +
      //         nosql.find(MongoDBObject("name" -> "Apache Cassandra",
      //                                  "type" -> "Column Database")).count)

      // 更新
      // $set
      nosql.update(MongoDBObject("name" -> "MongoDB"), $set ("url" -> "http://www.mongodb.org/"))
      // $unset
      nosql.update(MongoDBObject("name" -> "Memcached"), $unset ("type"))
      
      // save
      nosql.save(MongoDBObject("name" -> "Redis", "type" -> "Key Value Store"))
      nosql.findOne(MongoDBObject("name" -> "Redis"))
        .foreach(doc => nosql.save(doc += ("url" -> "http://redis.io/")))

      // ドキュメントの削除
      nosql -= MongoDBObject("name" -> "Apache HBase")
      // 以下でもOK
      // nosql.remove(MongoDBObject("name" -> "Apache HBase"))

      nosql.find.foreach(println)

      // コレクション内の全データを削除
      nosql -= MongoDBObject.empty
      // 以下でもOK
      // nosql.remove(MongoDBObject.empty)
    } finally {
      mongo.close()
    }
  }
}

基本的には、共通のimport文をひとつ書いてあげるスタイルのようです。
その他、Scalaのコレクションを拡張した形で実装されてたりしているので、他のドライバよりもちょっと毛色が違いますかね?

ちょっと、APIの使い方に迷う感じです…。

$setみたいな演算子もメソッドとして定義されていて、面白いんですけどね。

こんな感じで。以降使うなら、簡単にGroovyかな…。