これはG* Advent Calendar 2015の8日目の記事です。
はじめに
昨日の記事でGroovyからMongoDBを扱う基本を示しました。
今回は、さらにGroovyの機能を使ってよりGroovyな感じを掴んでみましょう!
なお、一番最後にコード全体を記載していますので、時間のない方はそちらを見て頂いてもOKです。
ドキュメントの生成をよりシンプルに
さて、昨日の記事でドキュメントを生成する際に、以下のコードを書きました。
// 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)
その結果生成されるドキュメント(JSON)は以下のようになります。
{
"name" : "MongoDB",
"type" : "database",
"count" : 1,
"info" : {
"x" : 203,
"y" : 102
}
}
いいねー!シンプルシンプル!
でも、よく見ると毎回フィールドごとにappend
書かなきゃイケないのが冗長ですね。
さらに入れ子の場合にはまたDocumentオブジェクト生成しないといけないし。。。
はい!あります!対応策あります!
と言ってもそんな難しい話ではなくて、実はDocumentクラスはコンストラクタでMapを受け付けてくれます。
さらに!Groovyの場合はMapがとてもお手軽に生成できます。
では、上記のコードと等価なGroovyのMapバージョンを見てみましょう!
def doc2 = [
"name": "MongoDB",
"type": "database",
"count": 1,
"info": ["x": 203, "y": 102]
]
collection.insertOne(new Document(doc2))
これです!これだけです!このコードは最初のappend
だらけのコードと全く同じです!
新しいMongoDBの公式JavaDriverと、Groovyの機能を組み合わせるとこんなにコードがシンプルになります!
JSON(テキスト)を保存する
GroovyのGStringを使えば、簡単にJSONをテキストとして表現できます。
GStringを利用するのには別に特別は宣言などは必要ありません。また、3つのダブルクォーテーションで囲んであげれば、改行やエスケープが簡単に記述できます。
そして、Documentクラスはスタティックメソッドとしてテキスト形式のJSONをパースするparse
メソッドがあるので、JSONテキストを渡してあげれまこれまたお手軽にMongoDBに保存できます。
String jsonAsText = """
{
"name": "Koji",
"age": 30,
"address":{
"city": "Tokyo",
"country": "Japan"
},
"married": true,
"favorites": ["Groovy", "Grails", "MongoDB"]
}
"""
collection.insertOne(Document.parse(jsonAsText))
通常のJSONをテキスト(String)として変数jsonAsText
に格納して、Document.parse()
に渡しているだけです。
これで、Mapを使ったDocument
インスタンスの生成と同じことが実現できています。
JSON(オブジェクト)を保存する
さて、コレは一体なんのことでしょう?
基本的に上記のテキストでJSONを表現する方法で、Stringの中で変数も扱えるので特に問題はないのですが、GroovyにはJsonBuilderという簡単にJSONオブジェクトを生成するクラスがあります。今度はこのJsonBuilderを使って同じことをしてみましょう。
// JsonBuilder経由
// Groovy上で、複雑なJSONを生成する場合、JsonBuilderがとっても便利。
def builder = new groovy.json.JsonBuilder()
builder {
name 'Koji'
age 30
address(
city: 'Tokyo',
country: 'Japan'
)
married true
favorites 'Groovy', 'Grails', 'MongoDB'
}
collection.insertOne(Document.parse(builder.toPrettyString()))
実行結果は上記の「JSON(テキスト)を保存する」と全く同じです。
JsonBuilderのインスタンスを生成して値をその中に記述していくだけです。
ちょっと書き方が独特で、通常JSONは "KEY: VALUE"の形式ですが、上記のJsonBuilderの書き方だとキーとバリューの間のコロンが必要なくなります。(が、address
の部分のようにそうでもない例外もあります。。。)
そしてDocument.parse
にtoStringした値を渡すだけでOKです。
ちなみに、JsonBuilderにはbuilder.toPrettyString
というメソッドが用意されていて、buiderの値を綺麗に表示することが出来ます。
// println builder.toPrettyString()を実行した結果
{
"name": "Koji",
"age": 30,
"address": {
"city": "Tokyo",
"country": "Japan"
},
"married": true,
"favorites": [
"Groovy",
"Grails",
"MongoDB"
]
}
JSON(テキスト)を編集して保存する
さて、最初の「JSON(テキスト)を保存する」の応用編です。
すでにString型変数jsonAsText
にJSONが格納されています。
この状態で値を変更するにはどうすればいいでしょうか?
テキストなので当然正規表現で置換することも出来ます。が、そんな面倒臭いことしなく無いですよね?
Groovyならそれを簡単に実現できます!
GroovyにはテキストのJSONをMapライクなオブジェクトに変換してくれるJsonSlurper
というクラスがあるのでそれを利用して実現できます。
ではコードを見てみましょう。
// JsonSlurper経由
// JsonSlurperを使うと、テキストで受け取ったJSONでも、通常のJSONオブジェクトのようにその中身を編集することができる
def slurper = new groovy.json.JsonSlurper().parseText(jsonAsText)
slurper.name = "JsonSlurper of Groovy!"
slurper.hoge = "piyo" // 存在しないフィールの追加もなんのその!
slurper.married = false
collection.insertOne(new Document(slurper))
JsonSlurper
のインスタンスを生成して、parseText
メソッドにすでに存在しているテキスト形式のJSONを渡すだけです。
あとは通常のGroovyのMapのように扱えます!
そしてこのJsonSlurper
インスタンスをnew Document
に渡してあげればちゃんとMongoDBに保存されます。
おまけ1(特定ドキュメントのUPDATE)
updateOne
やupdateMany
では、MongoDB上で対象ドキュメントの検索と更新が行われます。
では、該当ドキュメントの情報をGroovy上で加工して、さらにそのドキュメンをUPDATEするにはどうすればいいのでしょうか?
実はまだよく理解できていません。。。
一応、以下のコードでドキュメントを検索して、それぞれのtypeフィールドの内容を、それぞれの_id
フィールドの値を付加してUPDATEすることが出来ます。恐らく別の良いアプローチがあるとは思いますが、とりあえずコレでも問題はないはずです。
collection.find(eq("name", "MongoDB")).collect {
["_id":it._id, "newType": "NoSQL(ObjectId = ${it._id})"]
}.each {
collection.updateOne(eq("_id", it._id), set("type", it.newType.toString()))
}
おまけ2(JsonBuilderのComposite)
どういうことか?単純にJsonBuilderインスタンスの中で、別のJsonBuilderインスタンスを扱う、というだけです。
しかし、ちょっとクセがあって、JsonBuilderの中で利用される別のJsonBuilderは、.content
でその値を取得しなければならない、という点です。
なんの事だかワケ分かんないですよね。ソースを見てみましょう!
def builder2 = new groovy.json.JsonBuilder()
builder2 {
abc 'def'
fromOtherJsonBuilder builder.content
}
println builder2.toPrettyString()
collection.insertOne(Document.parse(builder2.toPrettyString()))
これで、1件新しいドキュメントがMongoDBに保存されます。
println builder2.toPrettyString()
で表示されるJSONは以下のとおりです。
{
"abc": "def",
"fromOtherJsonBuilder": {
"name": "Koji",
"age": 30,
"address": {
"city": "Tokyo",
"country": "Japan"
},
"married": true,
"favorites": [
"Groovy",
"Grails",
"MongoDB"
]
}
}
特に難しいことはないですよね。元々あったJsonBuilderオブジェクトを、別のJsonBuilderで包んだ形です。
fromOtherJsonBuilder builder.content
を見て分かるとおり、単純に別のJsonBuilderインスタンスから.content
でその中身を持ってくるだけでOKです。
元々あるJSONを別のJSONで包む今回のような例だと、テキストベースでも当然実現できますがコードがゴチャゴチャしてしまいがちです。
そういった際に、JsonBuilderが力を発揮します。
まとめ
いかがだったでしょうか。
Groovyを使えば、MongoDB用の公式Java用ドライバを使って、さらに便利、シンプルにMongoDBを扱えることがわかったのではないでしょうか。
最後に、今回のコードの全体を記述して終わりにさせていただきます。
ありがとうございました。
明日は@h1romas4さんです!
@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)
// 上の例と等価
def doc2 = [
"name": "MongoDB",
"type": "database",
"count": 1,
"info": ["x": 203, "y": 102]
]
collection.insertOne(new Document(doc2))
// テキストなJSONを保存する
String jsonAsText = """
{
"name": "Koji",
"age": 30,
"address":{
"city": "Tokyo",
"country": "Japan"
},
"married": true,
"favorites": ["Groovy", "Grails", "MongoDB"]
}
"""
collection.insertOne(Document.parse(jsonAsText))
// JsonBuilder経由
// Groovy上で、複雑なJSONを生成する場合、JsonBuilderがとっても便利。
// ちょっと書き方が独特で、通常JSONは "KEY: VALUE"の形式だけど、間のコロンが必要なくなる。
def builder = new groovy.json.JsonBuilder()
builder {
name 'Koji'
age 30
address(
city: 'Tokyo',
country: 'Japan',
)
married true
favorites 'Groovy', 'Grails', 'MongoDB'
}
collection.insertOne(Document.parse(builder.toString()))
// JsonSlurper経由
// JsonSlurperを使うと、テキストで受け取ったJSONでも、通常のJSONオブジェクトのようにその中編みを編集することができる
def slurper = new groovy.json.JsonSlurper().parseText(jsonAsText)
slurper.name = "JsonSlurper of Groovy!"
slurper.hoge = "piyo" // 存在しないフィールの追加もなんのその!
slurper.married = false
collection.insertOne(new Document(slurper))