DB2のNoSQLによるJSONサポート機能(第3部): Java APIを使用してアプリケーションを作成する

JSON文書(トランザクションを行うJSON文書とトランザクションを行わないJSON文書を含む)を管理する

アプリケーション環境が急速に変化するなか、さまざまなアプリケーション・レイヤー間でデータを保存・交換するための柔軟なメカニズムが必要となっています。JSON (Java Script Object Notation)はスキーマ設計のオーバーヘッドを削減し、データ加工を行う必要がないため、モバイル環境をサポートするインタラクティブなアプリケーションの構築のためには不可欠なテクノロジーとなっています。

DB2のNoSQLによるJSONサポート機能を活用すると、開発者はMongoDBに基づいて作成したポピュラーなJSONベースのクエリー言語を使用することによって、IBM DB2 for Linux, UNIX, and Windows に保存されたデータを処理するアプリケーションを作成することができます。ドライバー・ベースの本ソリューションは実績のあるエンタープライズ機能と高い質のサービスを提供するRDBMSと連携し、JSONによるデータの柔軟な表示を実現します。DB2のNoSQLによるJSONサポート機能は、JSON文書を処理するにあたってコマンドライン・プロセッサー、Java API、および通信リスナーをサポートします。

DB2によるJSONサポート機能に基づくJava APIはコマンドライン・プロセッサーと通信リスナーの基盤となる機能を提供し、カスタム・アプリケーションの作成をサポートします。本記事ではサンプルのJavaプログラムに基づいて基本的な使用方法について紹介し、JSON文書の保存と検索を最適化するためのオプションを説明します。

Marion Behnen, DB2 JSON and Spatial Development, IBM

Marion BehnenはIBMのソフトウェア・グループでシニア・ソフトウェア・エンジニアを務め、NoSQLによるJSONサポート機能を担当しています。NoSQLに取り組む前には、MarionはDB2アプリケーションとデータウェアハウス・アプリケーションのコンポーネントに関するテクニカル・リーダーを務めていました。IBMで勤務する以前は、Marionはビジネス・プロセスとデータ統合にさまざまな形で関わり、特に製造業のデータベース・アプリケーションの開発を担当していました。


developerWorks 貢献著者レベル

Henry Chiu, DB2 NoSQL JSON Development, IBM

Henry ChiuはIBMのソフトウェア・グループのソフトウェア・エンジニアです。IBM DB2によるNoSQLのJSONサポート機能に基づくAPIを担当する前は、HenryはDB2 for Linux, UNIX, and Windowsに関連するさまざまなクライアント製品(ODBC/CLIドライバー、JDBCドライバー、およびドライバー・ツールなど)に携わっていました。



Jyh-Chen Fang, DB2 NoSQL JSON Development, IBM

Jyhchen FangはIBMのソフトウェア・グループのソフトウェア・エンジニアです。IBM DB2によるNoSQLのJSONサポート・ソリューションを担当する前は、JyhchenはIBM Data Server Driver for JDBC and SQLJに関わっていました。



Manish Sehgal, DB2 NoSQL JSON Development, IBM

Manish SehgalはIBMに12年間以上勤務し、DB2のサーバー・ドライバー(JDBC)チームで設計・開発・顧客サポートを担当しています。現在ManishはカリフォルニアにあるIBMのシリコンバレー研修所でIBMによるNoSQLのJSONサポート機能に基づくAPI機能に取り組んでいます。IBMで勤務する前は、ManishはInformix社でInformixサーバーに対してCLI/ODBCドライバーを実装する業務に携わっていました。



Tony Sun, Staff Software Engineer, IBM

Tony Sunは、IBMのソフトウェア・グループでソフトウェア・エンジニアとして勤務しています。IBMが提供するDB2のNoSQLによるJSONサポート・ソリューションを担当する前は、TonyはIBM PureQueryやIBM Optim Performance ManagerなどさまざまなDB2のランタイム製品を担当していました。



2013年 10月 21日

はじめに

DB2のNoSQLによるJSONサポート機能が、DB2 for Linux, UNIX, and Windows(バージョン10.5)で提供されます。そのテクノロジー・プレビューを図1に示します。

  • JSONデータの管理と検索を行うコマンドライン・シェル
  • アプリケーション開発をサポートするJava API
  • ネットワーク経由で送信されたリクエストの受け付けと対応を行う通信リスナー
図1. DB2によるJSONサポート機能のコンポーネント
図1. DB2によるJSONサポート機能のコンポーネント

この記事では、Javaインターフェースを使用することによってDB2のJSON文書ストア内でJSON文書を管理・検索する方法について説明します。さらに提供されるACIDサポート機能について紹介し、トランザクション処理をサポートするオプションとスループットを改善するオプションと組み合わせる際の注意点についても説明します。


システムの前提条件

サンプル・アプリケーションを実行するには、IBM DB2 for Linux, UNIX and Windows(バージョン10.5以降)をインストール済みである必要があります。サンプル・アプリケーションのために使用可能なJSONをサポートするDB2データベースが既に存在している場合は、ホスト名またはIPアドレスおよびポート番号を確認のうえ、シンプルなJavaアプリケーションのサンプルを実行してください。JSONをサポートするDB2データベースが存在していない場合は、以下のステップを実行します。

  1. 環境変数を確認する
    • PATHにJavaランタイム環境(JRE 1.5以降)が含まれていること
    • CLASSPATHにdb2nosql.jarおよびJDBCドライバー(db2jcc.jarまたはdb2jcc4.jar)が含まれていること
  2. データベースを作成する
  3. データベースを有効化する

以下のサンプル・プログラムは、データベースの作成方法と有効化方法を示しています。

リスト1. データベースを作成・有効化する
CREATE DATABASE jsondb automatic storage yes using codeset utf-8 
          territory US collate using system   pagesize 32 k 

db2nosql -user bob –hostName bobshost -port 50000 -db jsondb -password mypwd -setup enable

さらに、本シリーズの第2部(コマンドライン・プロセッサーを使用する)を読んでください。


用語集

  • JSONストア: JSON文書のストアとして機能するDB2データベースを指します。JSON文書を処理するIBMのJSONソリューションを使用するには、まずJSONストアとして機能するDB2データベースにアクセスするために必要な接続情報と権限を取得する必要があります。
  • JSONの名前空間: コレクションを有効化するために使用されるDB2のSQLスキーマのことを指し、コンセプトとしてはMongoDBデータベースと似ています。
  • JSONのコレクション: コレクションとはDB2のテーブルのことを指し、コレクションには一覧の文書が含まれます。JSONのコレクションは柔軟な文書スキーマをサポートし、特定の構造を必要とはしません。

簡単なJavaアプリケーションのサンプル

本記事のダウンロード・セクションからサンプルのJavaプログラムをダウンロードしてください。db2cmdウィンドウで本サンプル・プログラムを起動するには、以下のステップを実行します。

  1. CLASSPATHを設定します。CLASSPATHにnosqljson.jarならびにdb2jcc.jarまたはdb2jcc4.jarを追加します。
    • Windowsシステムの場合 db2jcc.jarがC:\Program Files\IBM\SQLLIB\javaディレクトリーに存在し、nosqljson.jarがC:\Program Files\IBM\SQLLIB\json\libディレクトリーに存在する場合、以下のとおりCLASSPATHを設定します。
      set CLASSPATH=.;C:\Program Files\IBM\SQLLIB\java\db2jcc.jar;
      C:\Program Files\IBM\SQLLIB\json\lib\nosqljson.jar
    • UNIXシステムの場合 db2jcc.jarが/sqllib/javaディレクトリーに存在し、nosqljson.jarが/sqllib/json/libディレクトリーに存在する場合は、以下のとおりCLASSPATHを設定します。
      export CLASSPATH=.:/sqllib/java/db2jcc.jar:/sqllib/json/lib/nosqljson.jar
  2. テスト・ディレクトリーを作成し、本サンプル・プログラムを当該テスト・ディレクトリーにコピーします。
  3. 3. データベース名と接続情報を修正するために、サンプル・プログラムを修正します。
  4. テスト・ディレクトリーで本Javaサンプル・プログラムのコンパイルと実行を行います。
    • 本Javaサンプル・プログラムのコンパイルを行う方法 javac Sample.java
    • 本Javaサンプル・プログラムを実行する方法 java Sample

注釈付きのJavaサンプル・プログラム

本サンプル・プログラムは1つの文書を挿入し、当該文書を再度検索するためにクエリーを実行します。サンプル・コードの詳細については、Sample.javaを確認してください。

  1. DBオブジェクトを取得する DB2はSQLスキーマをJSONの名前空間として使用することによって、適切なコレクション名を作成します。そのため、コレクション情報と必要となる名前空間(DB2スキーマ)とともに、DBオブジェクトを取得する必要があります。
    DB db = NoSQLClient.getDB (databaseUrl, user, password, ns);
  2. コレクションのハンドルを取得する コレクションとはDB2のテーブルのことを指します。しかし、コレクションのハンドルを取得しても、コレクションは作成されません。コレクションが存在しない場合は、最初の文書が挿入された時点でコレクションが作成されます。このサンプル・プログラムの場合、コレクションは後ほど暗黙的に作成されます。コレクションを明示的に作成するには、db.createCollection(name, indexSpec)メソッドを使用します。
    DBCollection col = db.getCollection ("books");
  3. 文書を挿入する 文書を挿入しようとすると、コレクションが存在していない場合は、コレクションが自動的に作成されます。また、文書に_idフィールドが含まれていない場合、生成されたバイナリーの識別子を含む主キーとともにコレクションが作成されます。その後に挿入した文書に_idフィールドが含まれる場合は、当該_idフィールドのデータ型は生成された_idフィールドのデータ型と合致する必要があります。
    BasicDBObject json = new BasicDBObject (); 
    json.append ("author", "Smith"); 
    json.append ("title", "My first book");  
    System.out.println ("Inserting: " + json); 
    col.insert (json);
  4. クエリーを実行する 希望する検索条件にもとづいてオブジェクトを作成し、コレクションを検出するメソッドを呼び出します。クエリーを実行する際はまずhasNext()を呼び出し、検索結果はカーソル経由で提供されます。クエリーにはプロジェクション・リストは含まれないため、検索結果には生成された識別子を含む全ての属性が含まれます。
        DBCursor cursor = col.find (new BasicDBObject ("author", "Smith")); 
        try  
        {  
          while (cursor.hasNext ()) {  
            DBObject obj = cursor.next ();   
            System.out.println ("Retrieved: " + obj);  
          }  
        }  
        finally  
        {  
          cursor.close ();  
        }  
      }  
    }

出力結果のサンプル

  • 挿入データ: {"author":"Smith","title":"My first book"}
  • 抽出データ: {"_id":"$oid":"51bf710b416e9107ff9bc734"}, "author":"Smith","title":"My first book"}

基本的なコンセプト

DB2によるJSONサポートに基づくJava APIで使用されるJSONオブジェクト JSONの名前空間: DBオブジェクト

DBオブジェクトのハンドルを取得することによって、JSONストアにアクセスします。DBオブジェクトとは特定の JSONの名前空間(DB2スキーマ)のことを指します。本オブジェクトを使用することによって、コレクションの作成と削除を行い、トランザクションの起動とコミットを行い、バッチ処理に基づいてJSON文書を挿入するなどの処理を行うことができます。DBオブジェクトの全てはシステム内にキャッシュできるため、再利用ができます。

プログラムに基づいてDBオブジェクトのハンドルを取得するにはいくつかの方法があります。

  • JDBCベースのDataSourceオブジェクトを使用する
    javax.sql.DataSource ds =(javax.sql.DataSource)InitialContext.lookup("jdbc/DB2");
    com.ibm.json.nosql.DB db = NoSQLClient.getDB (ds);

    データソースをキーとして設定することによって、DBオブジェクトはシステム内にキャッシュされます。
  • JDBC URLを使用する
    com.ibm.json.nosql.DB db = getDB(jdbcUrl);

    URLとユーザーをキーとして設定することによって、DBオブジェクトはシステム内にキャッシュされます。
  • JDBCの接続オブジェクトを使用する
    java.sql.Connection con = 
           java.sql.DriverManager.getConnection(jdbcurl, user, password);
    com.ibm.json.nosql.DB db = NoSQLClient.getDB(con);

    URLとユーザーをキーとして設定することによって、DBオブジェクトはシステム内にキャッシュされます。

JSONのコレクション: コレクションのオブジェクト

DB2によるJSONサポート機能を使用すると、JSON文書はコレクション内に配置されます。各文書を個別識別できるように文書の識別子が必要になることを除いてJSONのコレクションは文書構造を必要としません。そのため、リレーショナル・テーブルのようにコレクションのテーブル構造を定義する必要はありません。しかしながら、コレクションに含まれる文書は通常共通の特徴を持つため、データの検出とグループ化がやりやすくなっています。

  • コレクションのオブジェクトのハンドルを取得する
    実際のコレクションは存在している場合もあれば、存在していない場合もあります。サンプル・プログラムは以下のとおりです。
    DBCollection coll = db.getCollection( "employee" );
  • 黙示的にコレクションを作成する
    コレクションが存在していない場合は、最初のJSON文書が挿入された時点で標準設定のコレクションが自動的に作成されます。このサンプル・プログラムでは、"Joe"という名前の従業員が含まれています。挿入処理によって以下のとおりWriteResult(実行ステータスとメッセージを含む)が戻されます。サンプル・プログラムは以下のとおりです。
    DBCollection employee = db.getCollection( "employee" ); 
    
    BasicDBObject operson = new BasicDBObject("firstname", "Joe");
    
    WriteResult wr = employee.insert(operson);
  • 明示的にコレクションを作成する
    標準設定に基づいてコレクションを自動的に作成することができます。しかし、カスタム設定に基づいて明示的にコレクションを作成した方がよいこともあります。コレクションを作成するには、以下のとおりcreateCollection()メソッドを使用します。その他に使用可能な特徴としては、識別子、テーブルスペース、バッファー・プール、圧縮機能などのデータ型に関するディレクティブが挙げられます。詳細情報については、参照資料を読んでください。サンプル・プログラムは以下のとおりです。
    // create a collection with this name and default characteristics
    DBCollection em1 = db.createCollection( "employee1" );
    
    // create a collection "employee1" with integer identifiers
    DBCollection dtc = _db.getCollection ("employee1");
    dtc.createCollection ("{_id:'$int'}");
  • コレクションをリネームする
    既存のコレクションに新しい名前をつけることができます。サンプル・プログラムは以下のとおりです。
    DBCollection em1 = db.getCollection( "employee1" ); 
    
    DBCollection em2 = em1.rename("employee2");
  • コレクションを削除する
    JSONストアから既存のコレクションを削除することができます。サンプル・プログラムは以下のとおりです。
    DBCollection c = db.getCollection( "employee" ); 
    c.drop();

JSONのフィールド: DBObjectオブジェクト

DBObjectを使用することによって、主要な値のペアに基づいてJSON文書を構築することができます。APIは本データをBSONのような形式に変換し、データベースに保存するようデータベースに提供します。データの抽出が完了すると、APIはバイナリーのBSONのようなデータをDBObjectに変換し、データ型を維持します。

注意: 保存形式はBSONとよく似ていますが、IBM独自の拡張機能がいくつか含まれています。

DBObjectに含まれるキーは、必ず文字列のデータ型である必要があります。データの値としては、DB2によるJSONサポート機能に基づくAPIはさまざまなデータ型(以下の表を参照)をサポートします。

表1. データ型
データ型JSON形式に基づく使用例
java.lang.String"string"
java.lang.Integer3
java.lang.Long4294967296
java.lang.Double6.2
java.lang.Booleantrue / false
java.lang.Byte []{ "$binary": "(base64-encoded value)", "$type": "0" }
java.util.Date (millisecond precision, in UTC) { "$date" : "1998-03-11T17:14:12.456Z" }
java.util.regex.Pattern{ "$regex" : "ab*" , "$options" : "" }
java.util.UUID{ "$uuid" : "fb46f9a6-13df-41a2-a4e3-c77e85e747dd" }
com.ibm.nosql.bson.types.ObjectId { "$oid" : "51d2f200eefac17ea91d6831" }
com.ibm.nosql.bson.types.Code { "$code" : "mycode" }
com.ibm.nosql.bson.types.CodeWScope { "$code" : "i=i+1", "$scope" : {} }
com.ibm.nosql.json.api.BasicDBObject { "a" : 1, "b": { "c" : 2 } }
com.ibm.nosql.json.api.BasicDBList [1 , 2, "3", "abc", 5]

データ文字列の値としては、クライアントは値をUTCに変換することによってDB2で保存可能なデータに変えることができ、本データはUTC形式の日付として抽出することもできます。

以下のサンプル・プログラムは、さまざまなデータ型を持つフィールドの値を挿入する方法を示しています。

リスト2. さまざまなデータ型のDBObjectを持つJSON文書を挿入する
BasicDBObject jsonObj = new BasicDBObject ();
jsonObj.append ("_id", new ObjectId ("51d2f200eefac17ea91d6831"));
    
SimpleDateFormat df = new SimpleDateFormat ("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
df.setCalendar (Calendar.getInstance (TimeZone.getTimeZone ("UTC")));
try {
  jsonObj.append ("date", df.parse ("1998-03-11T17:14:12.456Z"));
}
catch (ParseException e) {
  e.printStackTrace();
}
jsonObj.append ("pattern", Pattern.compile ("ab*", BSON.regexFlags ("")));
jsonObj.append ("code", new Code ("mycode"));
jsonObj.append ("codews", new CodeWScope ("i=i+1", new BasicBSONObject ()));
jsonObj.append ("null", null);
jsonObj.append ("uuid",
                UUID.fromString ("fb46f9a6-13df-41a2-a4e3-c77e85e747dd"));
jsonObj.append ("str", new String ("hello world"));
jsonObj.append ("int", new Integer (3));
jsonObj.append ("long", new Long (4294967296L));
jsonObj.append ("double", new Double (6,2));
jsonObj.append ("bool", new Boolean (true));
jsonObj.append ("subobject", 
new BasicDBObject ()
    .append ("a", 1)
    .append ("b", new BasicDBObject ().append ("c", 2))
  );
jsonObj.append ("array",
  new BasicDBList (1, 2, "3", "abc", 5)
  );
jsonObj.append ("objarray",
  new BasicDBList (
    new BasicDBObject ().append ("name", "Joe").append ("age", 5),
    new BasicDBObject ().append ("name", "Mary").append ("age", 4)
  ));

System.out.println ("Inserting: " + jsonObj);
c.insert (jsonObj);

DBObjectとして主要な値のペアを作成せずに、文字列の形式でJSON文書を挿入することもできます。APIは内部的に入力文字列を解析することによって、オブジェクトに変換します。以下のサンプル・プログラムは、文字列の表記に基づいてさまざまなデータ型を持つフィールドの値を挿入する方法を示しています。

リスト3. さまざまなデータ型を持つJSON文書を文字列として挿入する
String jsonString = 
"{"  
  + "_id : { $oid : \"51d2f200eefac17ea91d6831\" },"
  + "date : { $date : \"1998-03-11T17:14:12.456Z\" },"
  + "pattern : { $regex : \"ab*\" , \"$options\" : \"\" },"
  + "code : { $code : \"mycode\"},"
  + "codews : { $code : \"i=i+1\" , \"$scope\" : { }}," 
  + "null :  null,"
  + "uuid : { $uuid : \"fb46f9a6-13df-41a2-a4e3-c77e85e747dd\" },"
  + "str : \"hello world\","
  + "int: 3," 
  + "long : 4294967296," 
  + "double : 6.2," 
  + "bool : true," 
  + "subobject:{ a:1, b:{ c: 2 } },"
  + "array : [1 , 2, \"3\", \"abc\", 5],"
  + "objarray : [{name:\"Joe\", age:5}, {name:\"Mary\", age:4}]"   
 + "}";

System.out.println ("Inserting: " + jsonString);
c.insert (jsonString);

出力結果(読みやすくするために形式を整えてあります)

リスト4. JSON文書を文字列として挿入した結果の出力結果
Inserting: {_id : { $oid : "51d2f200eefac17ea91d6831" },
date : { $date : "1998-03-11T17:14:12.456Z"  },
pattern : { $regex : "ab*" , "$options" : "" },
code : { $code : "mycode"},
codews : { $code : "i=i +1" , "$scope" : { }},
null :  null,
uuid : { $uuid : "fb46f9a6-13df-41a2-a4e3-c77e85e747dd" },
str :  "hello world",
int: 3,
long : 4294967296,
double : 6.2,
bool : true,
subobject:{ a:1, b:{ c: 2 } },
array :  [1 , 2, "3", "abc", 5],
objarray : [{name:"Joe", age:5}, {name:"Mary", age:4}]}

文書の処理

JSON文書を挿入する

DB2によるJSONサポート機能に基づくAPIは、DB2にJSON文書を挿入することを目的としてDBCollection.insert()関数を提供します。各文書は一意性のある識別子を持ち、BSONに似た形式のBLOBとして保存されます。

コレクションに文書を挿入するにあたって、DB2によるJSONサポート機能に基づくAPIは自動的にテーブルを作成します(テーブルが存在していない場合)。この場合、最初に挿入される文書に_idフィールドが含まれる場合、当該フィールドは一意性のある識別子として使用されます。最初に挿入される文書に_idフィールドが含まれない場合は、一意性のある識別子が自動的に生成され、文書に生成された_idが挿入されます。いずれの場合も、その後挿入される文書にも同じデータ型の一意性のある_idフィールドが含まれる必要があります。サンプル・プログラムは以下のとおりです。

リスト5. 文書をコレクションに挿入する
DB db = NoSQLClient.getDB (jdbcUrl, user, pass);
DBCollection c = db.getCollection ("books");

// Create a document to insert
BasicDBObject json = new BasicDBObject ();
json.append ("isbn", "123-456-789");
json.append ("author", "Verne, Jules");
json.append ("title", "Journey to the Center of the Earth");
json.append ("abstract", "Classic science fiction novel in an unusual setting");
json.append ("price", 6.00);
json.append ("pages", 276);
json.append ("category", "Fantasy");

System.out.println ("Inserting: " + json);
    
// If the table "books" does not exist, it is automatically created 
c.insert (json);

出力結果(読みやすくするために形式を整えてあります)

リスト6. 出力結果(形式を整えたもの)
Inserting: {"isbn":"123-456-789",
"author":"Verne, Jules",
"title":"Journey to the Center of the  Earth",
"abstract":"Classic science fiction novel in an unusual  setting",
"price":6.0,
"pages":276,
"category":"Fantasy"}

JSON文書を検出する

DBCollectionは、コレクションに含まれるデータをカウント・検出・集計するためのさまざまなオプションを提供します。文書が満たす条件(クエリー)を設定し、どの属性を抽出すべきか(プロジェクション・リスト)定義することができます。

プロジェクション・リストを定義するには、属性を除外する場合は0を使用し、属性を検出するには1を使用します。属性の除外条件と検出条件を組み合わせることはできません。ただし、特定の条件に基づいて_idを除外し、それ以外の場合は自動的に_idを検出するように指定できます。

DBCursorを戻すメソッドを使用することによって、カーソル命令を通じて結果セットを制御することができます。本メソッドの例としては、結果セットの最大サイズを規定する(limit(n))やページ範囲の最大サイズを規定する(skip(n))が挙げられます。

DBCursorオブジェクトは前向きにのみ処理を繰り返し実行するカーソルであり、next/hasNextによってのみ実行されます。

以下のリストは、文書を検索する際の方法をいくつか示したものです。サポート対象の比較演算子とブーリアン演算子の一覧については、参考資料を参照し、本シリーズの第2部(コマンドライン・プロセッサーを使用する)を読んでください。

リスト7. 文書を検索する
// Find all documents with author 'Verne, Jules'
DBCursor cursor = col.find (new BasicDBObject ("author", "Verne, Jules"));


// Find all documents with author 'Smith' and price greater 6.20;
// include title and price (the _id is  automatically included)

BasicDBObject match = new BasicDBObject();
match.append("author", "Smith");
match.append("price", new BasicDBObject("$gt", 6.20));

BasicDBObject projection = new BasicDBObject();
projection.append("title", 1);
projection.append("price", 1);
DBCursor cursor2 = col.find (match, projection);


// Re-use the query, but exclude the _id 
projection.append("_id", 0);
cursor = col.find (match, projection);


// Find books with sales greater 1000, sort by sales, get first 2 only
BasicDBObject fproject = new BasicDBObject("author", 1);
fproject.put ("title", 1);
fproject.put ("sales", 1);
fproject.put ("rating", 1);
BasicDBObject cond = new BasicDBObject("sales", new BasicDBObject("$gt",1000));
     
DBCursor fcursor = dtc.find(cond, fproject);
fcursor.sort (new BasicDBObject("sales", 1));
fcursor.limit (2);

while (fcursor.hasNext()) {
   System.out.println(fcursor.next());
}


// Count entries where sales are less than 1000
long count = dtc.getCount (new BasicDBObject ("sales", 
      new BasicDBObject("$lt",1000)));


// Find distinct authors
List result = dtc.distinct("author");


// Get distinct data sorted by author, get first 2 entries only
BasicDBObject query = new BasicDBObject ("topic", "Mystery");
BasicDBObject keyObject = new BasicDBObject("author", 1);
keyObject.put ("rating", 1);
     
DBCursor dcursor = dtc.distinctAsCursor(keyObject, query);
dcursor.sort (new BasicDBObject("author", 1));

while (dcursor.hasNext()) {
   System.out.println(dcursor.next());
}

ネストされたオブジェクトや配列に含まれる値にアクセスするには、配列の値をピリオドを使用して表記します。つまり、属性名の後に0から始まる位置を付けます。例えば、[1, 2, "3", "abc", 5]という配列が存在する場合、“array.3”の値は"abc"になります。

ネストされたオブジェクトに含まれる値にアクセスするには、ピリオドを使用して完全なパスを指定します。例えば、{customer: {name: {"Joe"}, {state: "AZ"}}というネストされたオブジェクトが存在する場合、“customer.state”の値は"AZ"になります。


JSON文書を更新する

さらにDB2によるJSONサポートに基づくAPIでは、検索条件を満たすJSON文書を更新することを目的としてDBCollection.update()関数を提供します。文書内の特定のフィールドを更新・追加することもできれば、文書全体を置き換えることもできます。以下のサンプル・プログラムは、"isbn"の"123-456-789"という値で文書の"pages"フィールドを置き換えています。

リスト8. $set演算子を使用して特定のフィールドを更新する
DB db = NoSQLClient.getDB (jdbcUrl, user, pass);
DBCollection c = db.getCollection ("books");

// Update
c.update (new BasicDBObject ("isbn", "123-456-789"),
          new BasicDBObject ("$set", new BasicDBObject ("pages", 299)),
          false,
          true);

// Find document
cursor = c.find (new BasicDBObject ("isbn", "123-456-789"));
System.out.println ("Retrieved: " + cursor.next ());
cursor.close ();
リスト9. 出力結果(読みやすくするために形式を整えたもの)
Retrieved: {"_id":{"$oid":"51a7a4d9cd862910f0992b33"},
"isbn":"123-456-789",
"author":"Verne,  Jules",
"title":"Journey to the Center of the Earth",
"abstract":"Classic science fiction novel in an  unusual setting",
"price":6.0,
"pages":299,
"category":"Fantasy"}

update (upsert)関数の3つ目のパラメーターをtrueに設定すると、文書に対して以下の処理が行われます。

  • 文書が存在する場合は、文書が更新される
  • 文書が存在しない場合は、文書が挿入される

update (multi)関数の4つ目のパラメーターをtrueに設定すると、クエリーに合致する全ての文書が更新されます。本パラメーターをfalseに設定すると、クエリーに合致する最初の文書のみが更新されます。 $set演算子はフィールドが存在する場合は古い値を新しい値で置き換え、フィールドが存在しない場合は文書に当該フィールドを挿入します。$unset演算子は、特定のフィールドを削除するために使用することができます。

注意: $set演算子や$unset演算子を指定せずにupdate関数を使用すると、指定されたフィールドで文書のコンテンツを置き換えることができます。文書内の他のフィールドを保持するには、必ず$set演算子または$unset演算子を使用してください。


JSON文書の一部または全体を削除する

DBCollection.remove()関数を使用すると、コレクションからJSON文書を削除することができます。さらにremove関数を使用すると、クエリー・オブジェクトに基づいて文書の一部を削除対象として指定するための条件を指定できます。コレクションと関連するインデックスは全て保持されます。

例えば"books"というコレクションから全ての文書を削除するには、以下を指定します。

DB db = NoSQLClient.getDB (jdbcUrl, user, pass); 
DBCollection c = db.getCollection ("books");

c.remove ();

JSONファイルのインポートまたはエクスポートを行う

コレクションにデータをインポートすることもできれば、コレクションからデータをエクスポートすることもできます。その際使用されるデータ形式はJSONであり、ファイルは.jsの拡張子を使用します。

コレクションをインポートするには、メソッドとしてimportFileを使用します。

DBCollection dtc = dtc.getCollection("docs"); 

dtc.importFile("C:/docs.js",10);

本メソッドはdocs.jsという名前のファイルを検出のうえ、10回コミットを行うことによってコンテンツをインポートします。コミットの頻度を下げると、インポートのパフォーマンスを高めることができます。

インポート処理は1行ごとに読み込みを行うため、文書において改行などの行末文字の使用は避ける必要があります。JSON形式の基準に合致しない行ごとにエラーが発生し、識別子が重複する文書やコレクションとデータ型が合致しない識別子を持つ文書に対してエラーが発生します。エラーが発生した後も、処理は次の行に対して行われ、適切なデータがインポートされます。

以下の構造を持つコレクションに現在文書が含まれているとします。

Row 1:
{
"_id":1,
"x":1
}

かつ、インポート・ファイルに以下の文書が存在しているとします。

{"_id":"abc","abstract":"Spiders and Dragons","author":"Tolkien, 
J.R","isbn":"122-456-789","pages":216, "price":5.0,"title":"The Hobbit"}

すると、コレクションの_idが整数であるものの、書籍の_idが文字列であるため、インポート処理によって変換エラーが発生します。

インポートの実行中は、ネストされた文書を含め本文書の構造は維持されます。

コレクションをファイルにエクスポートするには、メソッドとしてexportFileを呼び出します。

DBCollection dtc = dtc.getCollection("docs");
dtc.exportFile("C:/exported.js")

本処理によって、"docs"という名前のコレクションを"exported.js"という名前のファイルにエクスポートすることができます。エクスポートされたファイルは、標準のJSONファイルです。


インデックスを使用する

JSON文書に含まれるフィールドをクエリーの検索条件として使用し、当該フィールドに対してインデックスを作成すると、大規模なワークロードのデータ抽出時間をスピードアップできることがよくあります。

インデックスの効果を高めるには、インデックスの条件を明確に設定する必要があります(フィールドの値として一意性の高い値を指定する必要があります)。一意性の高いフィールドを使用すると、クエリーの条件を満たす文書を検出する際にインデックスの読み取り回数が少なくて済みます。

インデックスによってデータ抽出のパフォーマンスを高めることができるものの、データの書き込みと保存のスピードは遅くなります。頻繁に更新される文書に対してあまりにも多くのインデックスを設定すると、処理を行うたびにインデックスを更新する必要が発生するため、データの挿入・更新・削除のスピードが遅くなることがあります。


インデックスを作成する

コレクションを作成すると、識別子に対するインデックスが一意性のあるインデックスとして自動的に作成されます。その他のフィールドに対してインデックスを追加するには、DBCollection.ensureIndex()関数を使用します。インデックスは1つ以上のフィールドに対して作成でき、昇順または降順でインデックスを設定することができます。

注意: 配列に含まれる要素に対してインデックスを設定することはできません。

インデックスを作成する場合、インデックスを設定する対象のフィールドのデータ型を指定する必要があります。クエリーで指定される検索対象の値がインデックスで指定されたデータ型と合致しない場合は、クエリーでインデックスは使用されません。

以下の例では、‘copy’フィールドは配列ではなく配列に含まれているわけでもないため、arrayのインデックス・オプションはfalseに設定する必要があります。

リスト10. 整数のデータ型を持つ‘copy’フィールドに対して昇順でインデックスを設定する
DB db = NoSQLClient.getDB (jdbcUrl, user, pass);
DBCollection c = db.getCollection ("books");

// insert 100 books, with different values for field 'copy'
for (int i = 1; i <= 100; i++)
{
  BasicDBObject json = new BasicDBObject ();
  json.append ("isbn", "123-456-711");
  json.append ("author", "Verne, Jules");
  json.append ("title", "Journey to the Center of the Earth");
  json.append ("abstract", "Classic science fiction novel in an unusual setting");
  json.append ("price", 6.00);
  json.append ("pages", 276 + i);
  json.append ("category", "Fantasy");
  json.append ("copy", i);
    
  System.out.println ("Inserting: " + json);
  c.insert (json);
}


// create index on field 'copy'
c.ensureIndex (
// 1 for ascending, $int is data type
new BasicDBObject ("copy", new BasicDBList (1, "$int")),
// index option 'array' explicitly set to false 
new BasicDBObject ("array", false));

// find document where 'copy' = 1
DBCursor cursor = c.find (new BasicDBObject ("copy", 1));

// call to DBCursor.next() will execute the query and utilize the index
System.out.println ("Retrieved: " + cursor.next ());
cursor.close ();

一意性のあるインデックスが必要な場合は、uniqueのインデックス・オプションをtrueに設定することができます。既存の文書が存在する場合、当該フィールドに関して値が重複していないことを確認してください。


インデックスを削除する

インデックスを削除するには、DBCollection.dropIndex()関数を使用します。

以下の例では、インデックスを作成する際と同じ条件に基づいてdropIndex()を使用しています。

    c.dropIndex (new BasicDBObject ("copy", new BasicDBList (1, "$int")));

注意: DBCollection.removeIndex()関数を使用しても、インデックスを削除することができます。


パフォーマンス機能とトランザクション

Lazy fetch

DBCollection.find()メソッドを使用してクエリーを実行すると、DBCursorオブジェクト(前向きにのみ処理を繰り返し実行するカーソル)が戻されます。標準のクエリーでは、選択された全てのデータがEager fetchに基づいてメモリーに提供されるメソッドが使用されます。カーソルを何度も使用することによって大規模な結果セットのブロックをフェッチするには、Lazy fetch (DBCursor.lazyFetch())を使用します

注意: Lazy fetchは、カーソルを開く前に指定する必要があります。さらに、結果をフェッチした後ではカーソルを閉じる必要があります。カーソルを閉じなければ、メモリー・リークが発生する場合があります。

以下の例は、カーソルに対してLazy fetchオプションを設定する方法について説明しています。

リスト11. JSON文書を抽出するためにLazy fetchを使用する
DB db = NoSQLClient.getDB (jdbcUrl, user, pass);
DBCollection c = db.getCollection ("customer");

DBCursor cur = c.find ().lazyFetch (); // specify lazy fetch

try
{
   // iterate through all documents
   while (cur.hasNext ())
   {
	// not pre-fetching, we fetch as we go
	DBObject cust = cur.next ();
	System.out.println (cust);
   }
}
finally
{
   // make sure to close the cursor to prevent leaks
   cur.close (); 
}

トランザクション

DB2のJSONストアにアクセスするには2種類のオプション(提供された接続情報を通じて明示的に設定される単一接続モードによるアクセスと共有接続プールによるアクセス)があります。

標準では共有接続プールが使用され、insert()やupdate()のような演算子は共有接続プール経由でアクセスを行い、共有接続プールを使用して処理を実行し、処理が完了すると処理結果を共有接続プールに戻そうとします。

単一接続モードによるアクセスの場合は、DB2によるJSONサポートに基づくAPIはJSON文書の処理に関するトランザクションの挙動を制御します。その結果、単一のトランザクションに基づいて複数の処理を実行し、自動コミットの挙動を制御し、エラーが発生した場合にロールバックを行うことができます。

表2. トランザクションの挙動を制御するデータベース上の機能
public void startTransaction()必要に応じて接続情報を取得し、自動コミットをfalseに設定します。 共有接続プール・モードやデータソース・モードが使用されている場合は、将来commitTransaction()やrollbackTransaction()が行われるまでデータベースを単一接続モードに設定します。
public void commitTransaction()startTransaction()が開始したトランザクションをコミットします。
public void rollbackTransaction()startTransaction()が開始したトランザクションをロールバックします。
public void setAutoCommit(boolean autoCommit)DB2が単一接続モードを使用している場合に、自動コミットを設定します。データソース・モードで本メソッドを呼び出すと、エラーが発生します。

注意: 上記の表で記載されるトランザクションAPIは、"Fire and Forget"モード(以下を参照)には適用されません。

トランザクションAPIを使用する際には、明示的に単一接続モードを使用することをお勧めします。トランザクションを開始することによって、共有接続プール・モードが単一接続モードに強制的に変更されることを防止するためです。

以下の例は、単一接続モードを使用したうえで、DB2によるJSONサポート機能に基づくAPIを使用したJavaプログラムのトランザクションをコミットする方法を示しています。db.commitTransaction()がスムーズに実行されると、Joe という顧客の文書とJoeによる冷蔵庫の発注に関する文書が顧客情報と発注情報のコレクションにそれぞれ挿入されます。これらの文書のいずれかでエラーが発生するといずれの文書も挿入されません。このAPIを使用すると、文書の全てが挿入されるか文書のいずれも挿入かされないかのいずれかが保証されます。

リスト12. 単一接続モードによるトランザクション
// use the url, user and password to create a JDBC connection.
Connection con = DriverManager.getConnection (url, user, password);
	
// get a db object with this connection
DB db = NoSQLClient.getDB (con); 

// get a handle to the affected collections
DBCollection dtc = db.getCollection("customer");
DBCollection dto = db.getCollection("order");

// start a transaction for this db object.
db.startTransaction ();
 try{
   // Create JSON object:
   BasicDBObject json = new BasicDBObject ();
   json.append ("name", "Joe");
   json.append ("cid", 12);
   dtc.insert(json);

   json = new BasicDBObject ();
   json.append ("product", "refrigerator");
   json.append ("cid", 12);
   dto.insert(json);
   db.commitTransaction ();
 } catch (Exception ex) {
   db.rollbackTransaction();
 } finally{
	
 }

“Fire and forget”モード

"Fire and forget"モードは複数スレッドに基づく非同期通信による挿入を実現し、本モードをコレクションに設定することによって挿入のパフォーマンスを高めることができます。本モードを使用するデメリットとしては、データがサーバーに書き込まれることが保証されないことが挙げられます。さらに、挿入中にエラーが発生してもアプリケーションは例外が発生したことを認識しません。しかし、データの損失を許容できるアプリケーションの活用シナリオの場合は、本モードを使用することによって得られるパフォーマンス上のメリットは大きいと言えます。

“Fire and forget”モードを有効化できるのは、アプリケーションが共有接続プールを使用している場合に限られます。アプリケーションが単一接続モードで“Fire and forget”モードを有効化した場合は、本モードの設定は無視され、挿入処理は単一スレッドで実行されます。

“Fire and forget”モードで使用されるスレッドの数は標準で10スレッドです。この値を変更するには、nosql.propertiesファイルに含まれるasyncMaxThreadCountを設定します。例えばスレッドの数を100に変更するには、nosql.asyncMaxThreadCount=100と設定します。

“Fire and forget”モードを有効化するには、コレクションはWriteConcernの値をNONEかNORMALに設定する必要があります。その他のWriteConcernの値(SAFEやJOURNAL_SAFEなど)を使用するとデータベースへの書き込みが保証されるため、“Fire and forget”モードは無効化されます。WriteConcernに関するより詳細な情報が必要な場合は、Javaに関する参考資料を読んでください。

以下の例では、"firenforget"という名前のコレクションに対応するコレクション・ハンドル(dtc)をDBオブジェクト(_db)から取得しています。NONEのオプションを指定したうえでdtc.setWriteConcernを呼び出すと、“Fire and forget”モードが有効化されます。

	DBCollection dtc = _db.getCollection("firenforget");
	dtc.setWriteConcern (WriteConcern.NONE);

“Fire and forget”モードで挿入を開始するには、他の挿入モードの場合と同じ挿入コードを使用します。

for(int i=0; i>100; i++){   
  DBObject obj = BasicDBObjectBuilder.start().
           append("name", "Joe"+i).
           add("nums", new  BasicDBList(i+1, i+2, i+3)).get();
 
  dtc.insert(obj);
  }
 _db.waitQueue();

上記のコードは複数のスレッドを使用して100個の文書を挿入します。このコードは“Fire and forget”モードを使用しない場合の挿入のコードと似ていますが、以下のステートメントに注目する必要があります。

_db.waitQueue()

アプリケーションは全ての挿入処理が実行されるまで待機し、挿入処理が完了した後でアプリケーションでコレクションを参照するためにコレクションへのアクセスが行われます。このメソッドを使用せずにDBCursor cursor = dtc.find(new BasicDBObject("nums", 1));のような検索を行うと、空のコレクションが戻される場合があります。find()メソッドが実行された後では、挿入処理が完了しなかった可能性があるためです。

上記の例のコードは挿入の際に発生した可能性のあるエラーを無視しています。挿入の際に発生した可能性のあるエラーを抽出するには、以下の例のとおりgetLastError()メソッドを使用します。

WriteResult wr = dtc.insert(obj); 

      CommandResult cr =  wr.getLastError ();

“Fire and forget”モードに関するより詳細な情報が必要な場合は、DB2によるJSONサポート機能に基づくJava APIに関する参考資料(WriteResultクラスとDBCollectionクラスに関する資料)を参照してください。


バッチ処理

バッチ処理を行うことによって文書を蓄積したうえで、個別の文書を別々に送信するのではなく複数の文書をまとめてJSONストアに送信することができます。DB2によるJSONサポート機能に基づくAPIは複数のJSON文書をまとめてバッチ処理するための体系的な方法を提供します。バッチ処理による主なメリットとしては、パフォーマンスが向上することが挙げられます。

文書に対してバッチ処理を行うには、バッチ処理の開始を示すためにstartBatch()を使用し、endBatch()を使用することによって文書の処理(挿入、更新、削除、または保存)を実行します。バッチに含まれるある処理が失敗した場合も、バッチに含まれる次の処理を実行することによって処理は継続します。

DB2によるJSONサポート機能に基づくAPIは2種類のバッチ処理(同種バッチおよび異種バッチ)をサポートします。

  • 同種バッチ: バッチ処理の対象となる全てのJSON文書が同じコレクションに含まれ、全ての文書に対して同じ処理が実行されます。

以下のサンプル・コードは、バッチ処理に基づいて3つの文書を1つのコレクションに挿入しています。

リスト13. 同種バッチの例
   DBCollection batch = db.getCollection("batch");
        
   BasicDBObject dbObj1 = new BasicDBObject("name", "Joe1");
   dbObj1.put("_id", 1);
    
   BasicDBObject dbObj2 = new BasicDBObject("name", "Joe2");
   dbObj2.put("_id", 2);
    	
   BasicDBObject dbObj3 = new BasicDBObject("name", "Joe3");
   dbObj3.put("_id", 3);
   
   db.startBatch();
    
   batch.insert(dbObj1);
   batch.insert(dbObj2);
   batch.insert(dbObj3);
   
   db.endBatch();
  • 異種バッチ: バッチ処理の対象となる一連のJSON文書が複数の異なるコレクションに含まれます。

以下のサンプル・コードは、バッチ処理に基づいて3つの文書を"batch1"のコレクションに挿入し、2つの文書を"batch2"のコレクションに挿入しています。

リスト14. 異種バッチの例
DBCollection batch1 = db.getCollection("batch1");
        
BasicDBObject dbObj1 = new BasicDBObject("name", "Joe1");
dbObj1.put("_id", 1);
    
BasicDBObject dbObj2 = new BasicDBObject("name", "Joe2");
dbObj2.put("_id", 2);
    	
BasicDBObject dbObj3 = new BasicDBObject("name", "Joe3");
dbObj3.put("_id", 3);

DBCollection batch2 = _db.getCollection("batch2"); 
   
BasicDBObject dbObj4 = new BasicDBObject("name", "Joe4");
dbObj4.put("_id", 4);
      
BasicDBObject dbObj5 = new BasicDBObject("name", "Joe5");
dbObj5.put("_id", 5);

db.startBatch();

// inserting documents to collection batch1
    
batch1.insert(dbObj1);
batch1.insert(dbObj2);
batch1.insert(dbObj3);

// inserting documents to collection batch2

batch2.insert(dbObj4);
batch2.insert(dbObj5);
   
db.endBatch();

上記の例では挿入処理をバッチ処理で行うケースを示していますが、更新・削除・保存などのその他の処理をバッチ処理で行うこともできれば、複数の処理を単一のバッチ処理にまとめることもできます。


結論

本記事はDB2によるJSONサポート機能に基づくJava APIの基本的な機能について紹介し、トランザクションを制御しスループットを高めるためのオプションについて説明しました。DB2によるJSONサポート機能についてより詳細な情報を確認し、コマンドライン・プロセッサーの使用方法や通信リスナーを使用したリクエストの送受信方法を確認するには、本シリーズに含まれるその他の記事を読んでください。DB2によるJSONサポート機能に基づくJavaインターフェースに関するより詳細な情報については、DB2によるJSONサポート機能に関する参考資料(特にJavaに関する参考資料)で確認することができます。


ダウンロード

内容ファイル名サイズ
Sample Java for this articleSample.zip1KB

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Information Management, Open source, Java technology
ArticleID=947457
ArticleTitle=DB2のNoSQLによるJSONサポート機能(第3部): Java APIを使用してアプリケーションを作成する
publish-date=10212013