これはG* Advent Calendar 2015の7日目の記事です。
昨日は yukungsさんの「Groovy で寒い冬を乗り越える話 #gadvent」でした。
こうやってハードウェアを扱える人は本当にかっこいいですね!
はじめに
Gのアドベントカレンダーではあるのですが、今後MongoDBに興味のある人がGoogleなどから辿り着いたときを想定して、以下の文章を書いています。
Gな人からするとちょっと違和感のある書き方かもしれませんがご了承ください。
2部構成で、今回はGroovyからJavaのmongo-java-driver
を用いてMongoDBを扱う基本、明日はさらにGroovyのライブラリやその文法からより簡単便利にMongoDBが扱えることを示します。
Groovyとは?
正式にはApache Groovyです。
JVM上で動く動的型付け言語で、Javaを更に便利にした言語です。
Groovyを動かすためにはJava8が必要です。逆に言えばJava8があれば動きます!
Java8がすでにインストールされているのであれば、SDKMANをインストールしましょう。
SDKMANはGroovyに関連するプロダクトに限らず、様々なJavaプラットフォームで動くプロダクトを簡単にインストール、管理できるツールです。
SDKMAN自体のインストールはとても簡単です。
SDKMANをインストールしたあとに、sdk intall groovy
を実行すれば、Groovyの最新バージョンがインストールされます。
Windowsユーザの方はposh-gvmを参照してください。
よくわからん!という方は以下の記事を参照しつつGroovy自体のインストーラを使ってインストールすることも出来ます。
インストールと実行(プログラミング入門 with Groovy)
GroovyとMongoDB
すでに述べましたが、GroovyはJavaを便利にした言語です。大胆に言い換えれば GroovyはJavaです。
つまり、JavaのMongoDB用ライブラリであるmongo-java-driver
をGroovyからもそのまま利用できます。
また、最近のmongo-java-driver
だと、ちょっと前のバージョンのmongo-java-driver
からは比べ物にならないくらい簡単にMongoDBが扱えるようになっています。
また、Groovyを用いることでさらに冗長な記述を減らすことが可能になります。
JavaでMongoDB使ったことがあるけど、最新のMongoDBドライバはまだ触ってない、
MongoDBは色々触ってるけど、まだプログラミング言語からはMongoDBを扱ったことがない、
という方にも参考になると思います。
そして、GroovyにはMongoDB用のサードパーティライブラリのGMongoがありますが、最新のmongo-java-driver
は、それ自体でかなり簡潔な書き方が出来ます。GMongo以外は使ったことがないというGroovyユーザの方にも参考になるのではと思います。
前提条件
今回、コードを実際に動かした環境は
Name | Value |
---|---|
OS | Linux Mint 17 |
MongogDB | 3.0.7 |
Java | 1.8.0_60 |
となっています。
一番上で述べましたが、事前にGroovyの最新版をインストールしておいてください。
また、今回のサンプルコードをサクサク実行するために、Groovyコンソール
の利用をおすすめします。
利用方法はこちらを参照してください。
Groovyの文法の話はここでは触れません。
ただ、それでもGroovyからMongoDBがとても簡単に扱えるということが分かると思います。
コーディング開始
さて!それではいよいよ始めていきましょう。
準備
まず、以下のコードを記述します。
@Grab(group='org.mongodb', module='mongo-java-driver', version='3.1.1')
import com.mongodb.MongoClient
import com.mongodb.client.MongoDatabase
import com.mongodb.client.MongoCollection
import org.bson.Document
import static com.mongodb.client.model.Filters.*
import static com.mongodb.client.model.Sorts.*
import static com.mongodb.client.model.Updates.*
import com.mongodb.client.result.UpdateResult
この記述だけで、mongo-java-driver
のダウンロードやパスの設定、今回サンプルで利用する必要なクラスのimportが完了します。
MongoDBに接続
それでは、さらに上記のコードの下部に以下を追記します。
なお、今後すべてのコードはどんどん既存のコードの下側に追記していってください。
// MongoDB本体に接続
MongoClient mongoClient = new MongoClient("localhost", 27017)
// 利用するデータベースに接続(データベース名はgroovy)
MongoDatabase db = mongoClient.getDatabase("groovy")
// 利用するコレクションに接続(コレクション名はtest)
MongoCollection<Document> collection = db.getCollection("test")
コレだけです!MongoDBの特徴として、特にデータベースとかコレクションが事前に存在していなくても大丈夫です。
また、Groovyは変数を宣言する際に型を指定する必要は無いので、以下のようにdefキーワード
を使って更にシンプルに書くことも出来ます。
def mongoClient = new MongoClient("localhost", 27017)
def db = mongoClient.getDatabase("groovy")
def collection = db.getCollection("test")
なお、この時点ではまだ groovyデータベース も testコレクション もMongoDB上には作成されていません。
コレクションに値を保存した段階で自動的にコレクションも作成されます。
ドキュメントの削除
いきなりですが、今後何度もサンプルコードを実行していくと、どんどんデータがMongoDB蓄積されて面倒です。
そのため、毎回データを削除するコードを書いておきましょう。
// DELETE ALL EXISTING DOCUMENTS
if (collection.count() > 0) {
collection.deleteMany(new Document())
}
これで、testコレクション
にドキュメントがある場合は、毎回削除されるようになります。
上記の内容はまだ今一わからないと思いますが、以下どんどん進めていけば分かってきますので気にせず先に進みましょう。
ドキュメントの保存
それではいよいよドキュメントを保存していきましょう!
// 1件登録する
Document doc = new Document("name", "MongoDB")
.append("type", "database")
.append("count", 1)
.append("info", new Document("x", 203).append("y", 102))
collection.insertOne(doc)
// 複数件一気に登録する
List<Document> documents = (1..10).collect { new Document("i", it).append("name", it) }
collection.insertMany(documents)
ドキュメントの登録だけでなく、更新、削除にも、 1件だけ 処理する為のメソッドと、 複数件一気に 処理するためのメソッドが用意されています。
上記のコードを見れば一目瞭然ですが、insertOne()
が一件だけ、insertMany()
が、一気にドキュメントを登録するメソッドになります。
MongoDBに登録するドキュメントを生成するために、Document
というクラスのインスタンスを生成します。
使い方はとても簡単で、第1引数にキー名、第2引数に値を渡します。インスタンス生成時のコンストラクタへ渡す場合でも、その後返される自身のインスタンスへ値を追加するappendメソッド
へも同様に値を渡していけば、MongoDB用のドキュメントが生成されます。
ドキュメントが入れ子になる場合は、上記のinfo
の部分のように、値にさらに新しいDocumentオブジェクトを指定することで可能になります。
上記のdoc
の中身は
{
"name" : "MongoDB",
"type" : "database",
"count" : 1,
"info" : {
"x" : 203,
"y" : 102
}
}
という感じのJSONになっています。
このJSON(Documentオブジェクト)をinsertOne
に渡すか、Listに格納してそのリストをinsertMany
に渡せば、MongoDBにドキュメントが保存されます。
この時点で、groovyデータベース も testコレクション がMongoDBに保存されました。
ドキュメントの取得
ドキュメントの取得、SQLで言う所のSELECT文ですね。
基本的にCollectionオブジェクトのメソッドであるfind
メソッドを実行するだけです。
find
を実行すると、Documentが格納されたリストが返されます。
さらにfirst
を実行すると、そのリストに格納されたDocumentから最初のDocumentオブジェクトを取得できます。
// 全件取得
assert 11 == collection.find().size()
// 最初のレコードを取得
Document myDoc = collection.find().first()
assert "MongoDB" == myDoc.name
簡単ですね。
では、検索する際に条件を指定するにはどうしたらいいでしょうか。
それも簡単で、以下のようにfindに条件をそのまま記述するだけです。
まるでMongoDBをmongoシェルから直接いじっているような流れるようなお手軽さですね!
myDoc = collection.find(eq('i', 5)).first()
assert 5 == myDoc.i
assert 45 == collection.find(gte('i',5)).sum{it.i} // シンプルな取得条件
assert 30 == collection.find( and(gt('i',5),lt('i',10)) ).sum{it.i} // ANDで複数の条件を指定
.sum{it.i}
という記述がありますが、コレはGroovyのメソッドです。
リストに格納されている各要素から、i
の合計を求める、という処理になっています。
MongoDBからGroovyへ流れるように自然に式が記述できるのが解ると思います。
Groovyを使えば合計を求めたりすることもこんなに簡単になります!
利用できるgt
やlt
といった比較用演算子についてはFiltersを参照してください。
次に、指定した値でソートして最初のドキュメントのiの値を取得する、というサンプルを以下に記載します。
// iというフィールドを持つドキュメントを、降順で取得する。そして、最初の値を取得。
// なお、sortとdescendingは、com.mongodb.client.model.Sortsクラスにある。
assert 10 == collection.find( exists('i') ).sort(descending('i')).first().i
ドキュメント指向なMongoDBなので、RDBMSと違ってドキュメントには特定のフィールドが合ったりなかったりします。
そこで、上記の例では、exists
でまずi
というフィールドを持つドキュメントを抜き出して、さらにiの降順に並び替え、そこから最初のドキュメントのiの値を取得しています。
sortの詳細についてはSortsを参照してください。
ドキュメントの更新
続いてドキュメントの更新です。SQLで言うところのUPDATE
です。
// UpdateOneの第1引数に条件式、第2引数にUPDATEで実行する式を与える。
// 存在しないフィールドを与えれば該当するドキュメントにそのフィールド追加される。以下はiフィールドが追加される
// フィールドが存在する場合は、単純にそのフィールドの値を上書きする。
collection.updateOne( eq('name', 'MongoDB'), set('i', 1) )
// UPDATEで複数のフィールドをアップデートしたい場合は、combineの中に記述する。
// nameフィールドがMongoDBというドキュメントを1件更新する。
// iフィールドと、countフィールドがそれぞれ1インクリメントされる。
collection.updateOne( eq('name', 'MongoDB'), combine( inc('i',1), inc('count',1) ) )
// 複数のドキュメントを一気にUPDATE
// iフィールドの値が8より大きいドキュメントのiフィールドの値を10インクリメントする。
UpdateResult ur = collection.updateMany(gt('i',8), inc('i',10)) // このサンプルではiが9と10のものは、iの値に10を加算する。
// 更新されたか確認してみる。なお、collectとsumはGroovyの便利なメソッド!
assert 39 == collection.find(gt('i',8)).sum{it.i}
assert 2 == collection.find(eq('name', 'MongoDB')).first().i
assert 2 == ur.matchedCount
上記のソースとコメントを見てもらえればなんとなく解ると思います。
ドキュメントを1件だけ更新したい場合はupdateOne
を、複数件一気に更新したい場合はupdateMany
を使用します。
それぞれ 第1引数に更新対象のドキュメントの条件 、 第2引数に更新する内容 を渡します。
利用できるinc
やset
といった更新系メソッドの一覧はUpdatesを参照してください。
SQLだとUPDATE TABLE_NAME SET i=1 WHERE i > 8
みたいな感じに、最初に更新内容、その後のWHEREで条件を指定するので逆になった感じですね。
ドキュメントの削除
すでにプログラムの一番上最初に全件削除のコードを書きましたね?
以下は、検索して該当するレコードのうち1件のみ削除する方法
// 1件削除
collection.deleteOne(gt('i', 8))
削除もドキュメントの作成や更新と同様に、1件だけの削除する場合はdeleteOne
、該当するドキュメントをすべて削除する場合はdeleteMany
となります。
その他
ドキュメントを更新、削除するときに条件を指定するのは別にいいけど、全件が対象の場合はどうなるの?という話ですが、一番最初のdeleteMany
のように、第1引数の条件のどころにnew Document()
を渡してあげれば、無条件ですべてのドキュメントが処理対象となります。
#まとめ
いかがだったでしょうか?
G*な人やJavaな人からは最新のJavaDriverを使えばかなりシンプルに処理が記述できる、MongoDBユーザの人からもGroovyを使えばかなりお手軽にプログラムからMongoDBを扱えることが伺えるのではないでしょうか?
しかしGroovyの便利さはコレだけにはとどまりません!明日はさらにGroovyの機能を使ったサンプルを投稿します!
最後に、現在のコードの全体を以下に記載しておきます。
@Grab(group='org.mongodb', module='mongo-java-driver', version='3.1.1')
import com.mongodb.MongoClient
import com.mongodb.client.MongoDatabase
import com.mongodb.client.MongoCollection
import org.bson.Document
import static com.mongodb.client.model.Filters.*
import static com.mongodb.client.model.Sorts.*
import static com.mongodb.client.model.Updates.*
import com.mongodb.client.result.UpdateResult
// MongoDB本体に接続
MongoClient mongoClient = new MongoClient("localhost", 27017)
// 利用するデータベースに接続(データベース名はgroovy)
MongoDatabase db = mongoClient.getDatabase("groovy")
// 利用するコレクションに接続(コレクション名はtest)
MongoCollection<Document> collection = db.getCollection("test")
// DELETE ALL EXISTING DOCUMENTS
if (collection.count() > 0) {
collection.deleteMany(new Document())
}
// 1件登録する
Document doc = new Document("name", "MongoDB")
.append("type", "database")
.append("count", 1)
.append("info", new Document("x", 203).append("y", 102))
collection.insertOne(doc)
// 複数件一気に登録する
List<Document> documents = (1..10).collect { new Document("i", it).append("name", it) }
collection.insertMany(documents)
assert 11 == collection.find().size()
// 最初のレコードを取得
Document myDoc = collection.find().first()
assert "MongoDB" == myDoc.name
myDoc = collection.find(eq('i', 5)).first()
assert 5 == myDoc.i
assert 45 == collection.find(gte('i',5)).sum{it.i} // シンプルな取得条件
assert 30 == collection.find( and(gt('i',5),lt('i',10)) ).sum{it.i} // ANDで複数の条件を指定
// iというフィールドを持つドキュメントを、降順で取得する。そして、最初の値を取得。
// なお、sortとdescendingは、com.mongodb.client.model.Sortsクラスにある。
assert 10 == collection.find( exists('i') ).sort(descending('i')).first().i
// UpdateOneの第1引数に条件式、第2引数にUPDATEで実行する式を与える。
// 存在しないフィールドを与えれば該当するドキュメントにそのフィールド追加される。以下はiフィールドが追加される
// フィールドが存在する場合は、単純にそのフィールドの値を上書きする。
collection.updateOne( eq('name', 'MongoDB'), set('i', 1) )
// UPDATEで複数のフィールドをアップデートしたい場合は、combineの中に記述する。
// nameフィールドがMongoDBというドキュメントを1件更新する。
// iフィールドと、countフィールドがそれぞれ1インクリメントされる。
collection.updateOne( eq('name', 'MongoDB'), combine( inc('i',1), inc('count',1) ) )
// 複数のドキュメントを一気にUPDATE
// iフィールドの値が8より大きいドキュメントのiフィールドの値を10インクリメントする。
UpdateResult ur = collection.updateMany(gt('i',8), inc('i',10)) // このサンプルではiが9と10のものは、iの値に10を加算する。
// 更新されたか確認してみる。なお、collectとsumはGroovyの便利なメソッド!
assert 39 == collection.find(gt('i',8)).sum{it.i}
assert 2 == collection.find(eq('name', 'MongoDB')).first().i
assert 2 == ur.matchedCount
// 1件削除
collection.deleteOne(gt('i', 8))
参考
Getting Started with MongoDB (Java Edition)
MongoDB Java Driver
Apache Groovy
SDKMAN
Filters
Sorts
Updates