DB2 NoSQL JSON 功能,第 3 部分: 使用 Java API 编写应用程序

管理 JSON 文档 - 使用事务和不使用事务

快速变化的应用程序环境需要一种灵活的机制来存储数据,并在不同的应用程序层之间传输数据。事实证明,JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种适用于移动、交互式应用程序的重要技术,它减少了模式设计的开销,消除了数据转换的需求。

DB2® NoSQL JSON 使开发人员能够使用由 MongoDB 创建的面向 JSON 的流行查询语言来编写应用程序,从而与 IBM DB2® for Linux®, UNIX®, and Windows® 中存储的数据进行交互。这个基于驱动程序的解决方案在 RDBMS 的上下文中实现了 JSON 数据表示的灵活性,具有著名的企业特性和服务质量。这项 DB2 NoSQL 功能支持使用一个命令行处理器、Java™ API 和有线监听器来处理 JSON 文档。

DB2 JSON Java API 是命令行处理器和有线监听器的支柱,它支持编写自定义应用程序。本文通过一个样例 Java 程序介绍基本的方法,讨论优化 JSON 文档的存储和查询的选项。

Marion Behnen, DB2 JSON 和空间开发, IBM

Marion Behnen 是 IBM 软件部的一名资深软件工程师,研究过多种 NoSQL JSON 特性。在钻研 NoSQL 领域之前,她是一些 DB2 和仓库应用程序组件的技术领导。加入 IBM 之前,她曾参与过业务流程和数据集成的许多方面的工作,特别是与制造行业中的数据库应用程序开发相关的某些方面。


developerWorks 投稿作者

Henry Chiu, DB2 NoSQL JSON 开发, IBM

Henry Chiu 是 IBM 软件部的软件工程师。在研究 IBM DB2 NoSQL JSON API 之前,他曾研究过各种 DB2 for Linux, UNIX, and Windows 客户端产品,比如 ODBC/CLI 驱动程序、JDBC 驱动程序和用于该驱动程序的工具。



Jyh-Chen Fang, DB2 NoSQL JSON 开发, IBM

Jyhchen Fang 是 IBM 软件部的一名资深软件工程师。在研究 IBM DB2 NoSQL JSON 解决方案之前,他曾研究过用于 JDBC 和 SQLJ 的 IBM Data Server Driver。



Manish Sehgal, DB2 NoSQL JSON 开发, IBM

Manish Sehgal 在 IBM 已工作 12 年多,在 DB2 服务器驱动程序 (JDBC) 团队担任过设计、开发和客户支持职位。他目前效力于加利福尼亚州硅谷实验室的 IBM NoSQL JSON API 团队。在加入 IBM 之前,他曾在 Informix 研究为 Informix 服务器实现 CLI/JDBC 驱动程序。



Tony Sun, 资深软件工程师, IBM

Tony Sun 是 IBM 软件部的一名资深软件工程师。在研究 IBM 的 DB2 NoSQL JSON 解决方案之前,他曾研究过各种 DB2 Runtime 产品,比如 IBM PureQuery 和 IBM Optim Performance Manager。



2013 年 9 月 17 日

概述

简介

DB2® JSON 是一个可用于 DB2 Linux, Unix and Windows 10.5 的 Technology Preview,提供了以下特性:

  • 一个命令行 shell 用于管理和查询 JSON 数据
  • 一个 Java™ API 用于应用程序开发
  • 一个有线监听器用于接受和响应通过网络发送的请求。
图 1. DB2 JSON 组件
应用程序使用有线监听器或 Java API 处理数据库中的 JSON 文档。Java API 也可供命令行接口使用。

本文将介绍如何使用 Java 接口管理和查询 DB2 JSON 文档存储中的 JSON 文档。还将讨论可用的 ACID 支持的某些方面,以及将事务支持选项与吞吐量提高选项相结合的一些限制。

先决条件

要运行样例应用程序,必须在系统上安装 IBM DB2 10.5 for Linux, UNIX and Windows(或更高版本)。如果已有可用于运行样例的启用了 JSON 的 DB2 数据库,请记住主机名称或 IP 地址和端口号,并继续运行样例 一个简单的 Java 应用程序样例。否则完成以下步骤。

  1. 检查环境变量:
    • 确保 PATH 中包含一个 Java Runtime Environment(最低 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 数据库。在可以开始使用 IBM JSON 解决方案处理 JSON 文档之前,需要获取必要的连接和授权信息来访问用作 JSON 存储的 DB2 数据库服务器。
  • JSON 命名空间:DB2 SQL 模式用于完全限定集合,在概念上类似于 MongoDB 数据库。
  • JSON 集合:一个集合包含一组文档,在一个 DB2 表中表示。JSON 集合支持灵活的文档模式,它们不会强制使用某种结构。

一个简单的 Java 应用程序样例

从本文的 下载 部分下载 Sample.java。要在 db2cmd 窗口中运行这个样例程序,请完成以下步骤:

  1. 设置 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. 修改样例程序,使之适合数据库名称和连接信息。
  4. 编译并运行这个测试目录中的 Java 样例程序。
    • 编译样例程序:
      javac Sample.java
    • 运行 Java 样例程序:
      java Sample

带注释的 Java 样例程序

此样例程序插入了一个文档,并提交了一个查询来再次查找它。请参见 Sample.java,查看完整的样例代码。

  1. 获取 DB 对象。
    请注意,DB2 使用 SQL 模式作为 JSON 命名空间来构建完全限定的集合名称。因此,DB 对象是通过连接信息和想要的命名空间(DB2 模式)来获得的。

    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. 提交一个查询。
    使用想要的搜索条件构建一个对象,并为该集合调用 find 方法。执行查询时首先调用 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+user,在内部缓存 DB 对象。

  • 使用一个 JDBC 连接对象:
    java.sql.Connection con = 
           java.sql.DriverManager.getConnection(jdbcurl, user, password);
    com.ibm.json.nosql.DB db = NoSQLClient.getDB(con);

    基于作为键的 url+user,在内部缓存 DB 对象。

JSON 连接:Collection 对象

借助 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 的二进制数据转换回 DBObjects,并保留数据类型。

备注:存储格式严格遵守 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. 插入一个包含各种数据类型的 JSON 文档作为 DBObject
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 提供了 DBCollection.insert() 函数,以便将 JSON 文档插入 DB2 中。每个文档使用惟一标识符存储为一个类似 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。

返回 DBCursor 的方法还允许通过游标指令控制结果集,包括设置结果集 (limit(n)) 和分页 (skip(n)) 的最大大小。

请注意,DBCursor 对象表示一个只能向前的游标,它对结果进行迭代,并且仅使用 next/hasNext 执行迭代。

下面的清单显示了一些搜索 JSON 文档的示例。关于受支持的比较和二进制运算符的列表,请参阅参考文档,另请参阅本系列的第 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 还提供了 DBCollection.update() 函数来更新满足搜索条件的 JSON 文档。文档中的特定字段可更新或附加,整个文档可替换。下面的示例显示了对一个 “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"}

如果更新的第 3 个参数 (upsert) 被设置为 true,则会对文档执行以下操作:

  • 如果文档存在,则更新,或者
  • 如果它不存在,则插入。

如果更新的第 4 个参数 (multi) 被设置为 true,那么与查询匹配的所有文档都将被更新。如果它被设置为 false,则仅更新与查询匹配的第一个文档。

如果该字段存在,$set 运算符会将旧值替换为新值;否则它将该字段插入文档中。$unset 运算符可用于删除特定的字段。

警告:如果使用没有 $set 或 $unset 运算符的更新,则会将文档的内容替换为指定的字段。如果打算保留文档中的其他字段,那么一定要使用 $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 来导入内容。提交频率越低,导入性能越高。

导入操作会逐行读取每个文件;因此文档中必须避免回车和类似的换行字符。对于每个不符合 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() 函数。可在单个字段上创建索引,也可在多个字段上按升序或降序创建索引。

备注:不支持为数组中的元素创建索引。

要创建索引,需要指定要对其创建索引的字段的数据类型。如果查询中的搜索值的类型与索引所指定的类型不同,那么在执行查询时不会使用索引。

在下面的示例中,索引选项 array 被设置为 false,因为字段 ‘copy’ 不是数组或包含在一个数组中。

清单 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() 也将丢弃索引。

性能特性和事务

延迟抓取

使用 DBCollection.find() 方法的查询会返回一个 DBCursor 对象,它表示一个迭代结果的仅向前的游标。默认情况下,查询会使用一个方法,在该方法中,所有已选中的数据都会被尽早抓取到内存中。要在迭代游标时抓取大型结果的数据块,可使用延迟抓取 (DBCursor.lazyFetch())。

警告:延迟抓取必须在打开游标之前指定。抓取结果后一定要关闭游标;否则可能发生内存泄漏。

下面的示例展示了如何在游标上设置延迟抓取选项。

清单 11. 使用延迟抓取检索 JSON 文档
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 存储:通过单一模式连接(使用所提供的连接信息显式建立)或通过共享连接池。

在默认情况下,会使用一个连接池,insert()update() 等操作将尝试从池中获取一个连接,使用该连接执行操作,然后在完成操作后将该连接返回到池中。

对于单一模式下的连接,DB2 JSON API 允许在处理 JSON 文档时控制事务行为。因此有可能在一个事务中组合多个操作,控制自动提交行为,并在发生错误时触发回滚。

表 2. DB 上控制事务行为的方法
public void startTransaction()如果需要,可获取一个连接,将 auto-commit 设置为 false。如果使用连接池或数据源,则将 DB 设置为单一连接模式,直到遇到一个未来的 commitTransaction() 或 rollbackTransaction()。
public void commitTransaction()提交 startTransaction() 启动的一个事务。
public void rollbackTransaction()回滚 startTransaction() 启动的一个事务。
public void setAutoCommit(boolean autoCommit)如果 DB2 使用单一连接模式,则设置 auto-commit。在数据源模式下调用此方法会发生错误。

备注:这个表中描述的事务 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)” 模式支持多线程、异步的插入,可在集合上设置该模式来增强插入的性能。使用此模式的不足之处在于,无法确保数据会写入到服务器中。此外,如果在插曲期间出现了错误,应用程序不会看到抛出的异常。但是,对于可容忍数据丢失的应用程序场景,使用此模式所带来的性能提升非常显著。

发后即忘模式仅可在应用程序使用连接池时激活。如果应用程序对单一连接启用发后即忘模式,该模式设置会被忽略,插入操作将以单线程形式执行。

用于发后即忘模式的线程数量默认情况下为 10。这个值可通过设置 nosql.properties 文件中的 asyncMaxThreadCount 来更改。例如,要将线程数设置为 100,可使用 nosql.asyncMaxThreadCount=100

要启用发后即忘模式,集合必须将 WriteConcern 值设置为 NONENORMAL。其他 WriteConcern 值(比如 SAFEJOURNAL_SAFE)会禁用发后即忘模式,因为它们要确保数据被写入数据库中。请参阅 Java 文档,了解 WriteConcern 的更多信息。

在下面的示例中,从 DB 对象 _db 获得了一个映射到名为 “firenforget” 的集合的集合句柄。当使用选项 NONE 调用 dtc.setWriteConcern 时,将会启用发后即忘模式。

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

要开始在发后即忘模式下执行插入操作,可使用与其他插入模式相同的插入代码:

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 个文档。该代码类似于非发后即忘模式的插入,但是请注意这条语句:

_db.waitQueue()

应用程序将等待所有插入完成处理,应用程序中对集合的后续引用只在完成插入后才会访问该集合。没有此方法调用, DBCursor cursor = dtc.find(new BasicDBObject("nums", 1)); 这样的搜索可能返回一个空集合,因为在执行 find() 方法后,插入操作可能仍为完成状态。

上述示例中的代码忽略了潜在的插入错误。要检索在处理期间可能发生的插入错误,可使用 getLastError() 方法,如下面的样例中所示。

      WriteResult wr = dtc.insert(obj); 

      CommandResult cr =  wr.getLastError ();

有关发后即忘模式的更多细节,请参阅 DB2 JSON API 的 Java 文档中的 WriteResult 类和 DBCollection 类。

批处理

批处理会累积一些文档,然后将多个文档一起发送到 JSON 存储,而不是单独发送每个文档。DB2 JSON API 通过一种程序化的方式来实现对多个 JSON 文档的批处理。批处理的主要优势是性能更高。

要批处理文档,可应用 startBatch() 来标记一个批次的起点,应用 endBatch() 来触发对文档的操作(插入、更新、删除或保存)的执行。请注意,如果批次中的某个操作失败,将会继续执行批次中的下一个操作。

DB2 JSON API 支持两种类型的批处理:同构批处理 (homogenous batching) 和异构批处理 (heterogenous batching)。

  • 同构批处理:这意味着加入批次的所有 JSON 文档都包含在同一个集合中,同一个操作会应用到所有文档上。

    下面的示例代码使用批处理在一个集合中插入 3 个文档:

    清单 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” 中,将两个文档插入到集合 “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 文档。


下载

描述名字大小
本文的样例 Java 代码Sample.zip1KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management, Open source, Java technology
ArticleID=945300
ArticleTitle=DB2 NoSQL JSON 功能,第 3 部分: 使用 Java API 编写应用程序
publish-date=09172013