Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
5
Help us understand the problem. What is going on with this article?
@saba1024

[Groovy] MongoDBを簡単に扱えるイケてる言語Groovy -Groovyの応用編-

More than 5 years have passed since last update.

これは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)

updateOneupdateManyでは、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))
5
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
saba1024
ドイツでWeb系エンジニアとして働いている日本人です。 基本プログラマですがシスアドっぽいこともしています。 大体Groovy、Grails関連かサーバ関連の内容を書いています。 _(:3」∠)_ =3 ブーッ

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
5
Help us understand the problem. What is going on with this article?