JavaからMongoDBへのアクセス（接続、検索、insert、update、delete）


[Java MongoDB Driver](http://docs.mongodb.org/ecosystem/drivers/java/)を使ってJavaからMongoDBにアクセスする方法について、全くはじめての人向けメモ。
MongoDBには色々機能があるようですが、今回はJava MongoDB Driverを使った簡単なCRUDの記述方法を解説します。

#MongoDBのこと
RDBとは用語が異なります。
何も知らずにJava MongoDB Driverのドキュメント読んでも？？？となってしまうので、最低限、RDBとの用語の違いを把握しておきましょう。

RDBとMongoDBのデータベース構造の比較は以下の通り。

| RDB | MongoDB |
|:-----------|:------------|
| データベース | データベース |
| テーブル |コレクション |
| レコード | ドキュメント |


また、MongoDBの特徴などは以下のようなサイトで把握します。

- [Introduction to MongoDB — MongoDB Manual 2.6.3](http://docs.mongodb.org/manual/core/introduction/)
- [触ってみよう！ビッグデータを支えるクラウド技術 - MongoDBを試してみよう：ITpro](http://itpro.nikkeibp.co.jp/article/COLUMN/20120306/384811/)
- [MongoDBでゆるふわDB体験：連載｜gihyo.jp … 技術評論社](http://gihyo.jp/dev/serial/01/mongodb)



#build.gradleの設定
build.gradleに以下の設定を追加します

```
dependencies {
...
	compile group: 'org.mongodb', name:'mongo-java-driver', version:'2.12.3'
...
}
```

今回はGradleを利用しましたが、自分で依存関係を解決する場合は[MongoDB Drivers](http://docs.mongodb.org/ecosystem/drivers/)から適切なドライバをダウンロードしてパスを通します。

#JavaからMongoDBにアクセス

## サーバとの接続・切断

```java
// MongoDBサーバに接続
MongoClient client = new MongoClient("localhost", 27017);
// 利用するDBを取得
DB db = mongoClient.getDB( "mydb" );

...


// サーバから切断
client.close();
```

- new MongoClient()でコネクションプールのコネクションを1つをとってくる（らしい）
- スレッドセーフで、複数のスレッドで共有できるよう設計されているので、1つのアプリで1つのインスタンスでOK（らしい）
- getDB()したタイミングでデータベースがなければ勝手に生成される

詳しくは[Getting Started with Java Driver](http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-java-driver/#getting-started-with-java-driver)参照。

##認証
セキュアモード（注：[Security Tutorials — MongoDB Manual 2.6.3](http://docs.mongodb.org/manual/administration/security/)）で起動している場合は認証が必要。

```java
MongoClient mongoClient = new MongoClient();
DB db = mongoClient.getDB("mydb");
boolean auth = db.authenticate(myUserName, myPassword);
```

##コレクション一覧取得
データベース内のコレクション一覧を取得

```java
Set<String> colls = db.getCollectionNames();

for (String s : colls) {
    System.out.println(s);
}

```

##指定のコレクション取得
取得したコレクションに対してCRUD等の操作ができます。

```java
DBCollection coll = db.getCollection("testCollection");
```


##ドキュメントのInsert
取得したコレクションにドキュメントをインサートします。
以下の構造のドキュメントをインサートする場合、

```JSON
{
   "name" : "MongoDB",
   "type" : "database",
   "count" : 1,
   "info" : {
               "x" : 203,
               "y" : 102
             }
}
```

Javaのソースは以下になります。

```java
BasicDBObject doc = new BasicDBObject("name", "MongoDB")
        .append("type", "database")
        .append("count", 1)
        .append("info", new BasicDBObject("x", 203).append("y", 102));
coll.insert(doc);
```

[BasicDBObject](http://api.mongodb.org/java/current/com/mongodb/BasicDBObject.html) クラスを使ってデータ構造を作ります。
その後、インサート対象のコレクションの```insert()```メソッドを呼び出すだけです。
が、はっきりいってDBObjectを作るのがとてもめんどくさい。。

JSON文字列からDBObjectをつくるには、以下のように記述します。

```java
DBObject json = (DBObject) JSON.parse("{'name':'mkyong', 'age':30}");
coll.insert(json);
```


##ドキュメントの検索
###1件目のドキュメントを検索
コレクションから1件だけドキュメントを取得するには、以下のように記述します。

```java
DBObject myDoc = coll.findOne();
System.out.println(myDoc);
```

###カーソルを使って全てのドキュメントを取得
コレクションから全てのドキュメントを取得するには```find()```メソッドを利用します。
```find()```メソッドでは、クエリに一致した全てのドキュメントにアクセスできます。

```java
DBCursor cursor = coll.find();
try {
   while(cursor.hasNext()) {
       System.out.println(cursor.next());
   }
} finally {
   cursor.close();
}
```


###ドキュメントの件数を取得する
コレクションに何件のドキュメントがあるか調べるには```getCount()```メソッドを利用します。

```java
long count = coll.getCount();
System.out.println(count + "件");
```

###クエリを使って1件のドキュメントを取得する

```java
BasicDBObject query = new BasicDBObject("i", 71);

cursor = coll.find(query);

try {
   while(cursor.hasNext()) {
       System.out.println(cursor.next());
   }
} finally {
   cursor.close();
}
```

###$オペレータを使ったクエリでドキュメントを検索する
MongoDBではクエリに$オペレータを利用して検索や集計等が行えます。
例えば、"qty"フィールドが20でないドキュメントを検索するには以下のように記述します

```
db.inventory.find( { qty: { $ne: 20 } } )
```

オペレータについての詳細は[Operators — MongoDB Manual 2.6.3](http://docs.mongodb.org/manual/reference/operator/) 参照。

$オペレータはDBObjectに、通常のStringキーとして設定します。

```java
query = new BasicDBObject("name",                                   
		new BasicDBObject("$ne", "mkyong")).append("count",         
		new BasicDBObject("$gt", 1));                               
cursor = coll.find(query);                                          
try {                                                               
	while (cursor.hasNext()) {                                      
		System.out.println("use $ operrator -> " + cursor.next());  
	}                                                               
} finally {                                                         
	cursor.close();                                                 
}                                                                   
```

でも、DBObjectを生成するのはやっぱり面倒なので、JSONからクエリを生成します。

```java
json = (DBObject) JSON                                              
		.parse("{'name':{$ne:'mkyong'}, 'count':{$gt:1}}");         
cursor = coll.find(json);                                           
try {                                                               
	while (cursor.hasNext()) {                                      
		System.out.println("use $ ope(JSON) -> " + cursor.next());  
	}                                                               
} finally {                                                         
	cursor.close();                                                 
}                                                                   
                                                                    
coll.remove(new BasicDBObject());                                   
```

##ドキュメントUpdate
ドキュメントを更新するには、更新対象のDBObjectを取得して書き換え、```save()```メソッドで更新します。
このとき、_idは変更してはいけません。DBObjectの_idが一致するドキュメントがあった場合に上書きされるからです。

```java
doc = coll.findOne();                              
doc.put("name", "MongoDB2"); // name属性を更新       
doc.put("age", "15");        // age属性を追加        
coll.save(doc);                                    
```

##ドキュメントDelete
ドキュメントを削除するには、削除対象のDBObjectを取得して、```remove()```メソッドで削除します。

```java
doc = coll.findOne();
coll.remove(doc);
```

##コレクション削除
コレクションを削除する場合```drop()```メソッドを利用します。

```java
System.out.println(db.getCollectionNames()); 
coll.drop();                                           
System.out.println(db.getCollectionNames()); 
```

##サンプルコード
上記の処理まとめ。（認証無し）

```java
MongoClient client = null;                                               
                                                                         
try {                                                                    
	// MondoDBサーバへの接続                                                    
	client = new MongoClient("localhost", 27017);                        
	// client = new MongoClient();                                       
	// 使用するDBの指定                                                         
	DB db = client.getDB("mydb");                                        
                                                                         
	DBCollection coll = db.getCollection("testCollection");              
                                                                         
	// Insert(append版)                                                   
	DBObject doc = new BasicDBObject("name", "MongoDB")                  
			.append("type", "database")                                  
			.append("count", 1)                                          
			.append("info",                                              
					new BasicDBObject("x", 203).append("y", 102));       
	coll.insert(doc);                                                    
                                                                         
	doc = new BasicDBObject("name", "MongoDB")                           
			.append("type", "database")                                  
			.append("count", 2)                                          
			.append("info",                                              
					new BasicDBObject("x", 400).append("y", -10));       
	coll.insert(doc);                                                    
                                                                         
	// Insert(JSON版)                                                     
	DBObject json = (DBObject) JSON                                      
			.parse("{'name':'mkyong', 'age':30}");                       
	coll.insert(json);                                                   
                                                                         
	// 1件だけ検索                                                            
	DBObject myDoc = coll.findOne();                                     
	System.out.println("findOne() -> " + myDoc);                         
                                                                         
	// カーソルを使って全件取得                                                      
	DBCursor cursor = coll.find();                                       
	try {                                                                
		while (cursor.hasNext()) {                                       
			System.out.println("DBCursor - > " + cursor.next());         
		}                                                                
	} finally {                                                          
		cursor.close();                                                  
	}                                                                    
                                                                         
	// ドキュメント件数取得                                                        
	long count = coll.getCount();                                        
	System.out.println(count + "件");                                     
                                                                         
	// クエリを使って1件のドキュメントを取得する                                             
	BasicDBObject query = new BasicDBObject("name", "MongoDB");          
	cursor = coll.find(query);                                           
	try {                                                                
		while (cursor.hasNext()) {                                       
			System.out.println("use query -> " + cursor.next());         
		}                                                                
	} finally {                                                          
		cursor.close();                                                  
	}                                                                    
                                                                         
	// $オペレータを使ったクエリ                                                     
	query = new BasicDBObject("name",                                    
			new BasicDBObject("$ne", "mkyong")).append("count",          
			new BasicDBObject("$gt", 1));                                
	cursor = coll.find(query);                                           
	try {                                                                
		while (cursor.hasNext()) {                                       
			System.out.println("use $ operator -> " + cursor.next());    
		}                                                                
	} finally {                                                          
		cursor.close();                                                  
	}                                                                    
                                                                         
	// JSONからクエリを生成                                                      
	json = (DBObject) JSON                                               
			.parse("{'name':{$ne:'mkyong'}, 'count':{$gt:1}}");          
	cursor = coll.find(json);                                            
	try {                                                                
		while (cursor.hasNext()) {                                       
			System.out.println("use $ ope(JSON) -> " + cursor.next());   
		}                                                                
	} finally {                                                          
		cursor.close();                                                  
	}                                                                    
	                                                                     
	// ドキュメントをアップデート                                                     
	doc = coll.findOne();                                                
	doc.put("name", "MongoDB2");	// name属性を更新                         
	doc.put("age", "15"); 			// age属性を追加                          
	coll.save(doc);                                                      
	cursor = coll.find();                                                
	try {                                                                
		while (cursor.hasNext()) {                                       
			System.out.println("updated -> " + cursor.next());           
		}                                                                
	} finally {                                                          
		cursor.close();                                                  
	}                                                                    
	                                                                     
	// ドキュメントを削除                                                         
	doc = coll.findOne();                                                
	coll.remove(doc);                                                    
	cursor = coll.find();                                                
	try {                                                                
		while (cursor.hasNext()) {                                       
			System.out.println("deleted -> " + cursor.next());           
		}                                                                
	} finally {                                                          
		cursor.close();                                                  
	}                                                                    
                                                                         
	// コレクション削除                                                          
	System.out.println("コレクション削除前: " + db.getCollectionNames());         
	coll.drop();                                                         
	System.out.println("コレクション削除後: " + db.getCollectionNames());         
                                                                         
	coll.remove(new BasicDBObject());                                    
                                                                         
} catch (UnknownHostException e) {                                       
	// TODO Auto-generated catch block                                   
	e.printStackTrace();                                                 
} finally {                                                              
	if (client != null) {                                                
		client.close();                                                  
	}                                                                    
}                                                                        
```

#その他、気になったこと
上記以外に気になって調べたことのメモ。
##トランザクション
MongoDBにはトランザクションがなく後勝ちなので、楽観ロックで排他制御するようです。
[（4/4）触ってみよう！ビッグデータを支えるクラウド技術 - MongoDBを試してみよう：ITpro](http://itpro.nikkeibp.co.jp/article/COLUMN/20120306/384811/?ST=bigdata&P=4)参照
>（1）クエリーにより、ドキュメントを取り出す。
（2）古い値を保存しておく
（3）ドキュメントを更新
（4）古い値（_idを含む）を第1引数に、新しい更新したドキュメントを第2引数に指定して、updateメソッドを呼ぶ。ドキュメントが書き換わっていたらupdateが失敗する
（5） updateが失敗したら、また（1）からやり直す。

だそうです。

また、一連の操作中にエラーが起きてもその前の処理をロールバックできません。
```db.getLastError();```メソッドで最後に起こったエラーが確認できるようです。


##save()とinsert()、update()の違い
save()メソッドと、insert()、update()メソッドが出てきて使い分けがよくわからなかったのでまとめ。
詳細はjavadoc参照。（[DBCollection](http://api.mongodb.org/java/current/com/mongodb/DBCollection.html#update(com.mongodb.DBObject, com.mongodb.DBObject, boolean, boolean))）

|  | save() | insert() | update() |
|:-----------|:-----------|:------------|:------------|
| 更新できる件数 | 1件 | 1件以上 | 1件以上(*1) |
| 対象の指定 | ドキュメント | ドキュメント | クエリ |
| 対象レコードの有無等 | _idが無ければInsert、あればUpsert | - | 指定したクエリを発行した時点で取得される_idに対応するドキュメントが、Update実行時コレクションに無い場合は更新失敗*2 |


*1:updateMulti()メソッドを利用するか、update()メソッドの第4引数にtrueセットで複数件更新。それ以外の場合は最初の1件だけ更新。
*2:update()メソッドの第3引数をtrueに設定するとUpsertされる

##Bulkオペレーション
- 大量のデータを操作する場合はBulkオペレーションを利用すると速いらしい
- insert/update/removeが一括で処理できる
- Buklオペレーションには2種類ある
 - Ordered bulk operation：順序を保証
 - Unordered bulk operations：順序を保証しない（並行処理）

StatementのaddBatch()、executeBatch()っぽい。
サンプルコードは以下の通り。

```java
// 1. Ordered bulk operation
BulkWriteOperation builder = coll.initializeOrderedBulkOperation();
builder.insert(new BasicDBObject("_id", 1));
builder.insert(new BasicDBObject("_id", 2));
builder.insert(new BasicDBObject("_id", 3));

builder.find(new BasicDBObject("_id", 1)).updateOne(new BasicDBObject("$set", new BasicDBObject("x", 2)));
builder.find(new BasicDBObject("_id", 2)).removeOne();
builder.find(new BasicDBObject("_id", 3)).replaceOne(new BasicDBObject("_id", 3).append("x", 4));

BulkWriteResult result = builder.execute();

// 2. Unordered bulk operation - no guarantee of order of operation
builder = coll.initializeUnorderedBulkOperation();
builder.find(new BasicDBObject("_id", 1)).removeOne();
builder.find(new BasicDBObject("_id", 2)).removeOne();

result = builder.execute();
```