转载

NoSQL 数据库 Cloudant

Cloudant 简介

IBM Cloudant 是一种基于 json document 类型的非关系型(NoSQL)数据库,其具有在云端高效处理高负载、高并发读写的强大特性。从另一个角度来说,Cloudant 还是一个开源的、分布式的数据库,它基于 Apache 的 Couch DB 项目以及开源的 BigCouch 项目。比如将其应用于一个大型的数据量快速增长的 web 或 mobile 项目之中,无疑将是一个非常明智的选择。

因此,从业务场景的角度来看,我们不难发现,在具有大规模数据处理、大规模实时动态数据处理、以及高并发性访问的应用场景中,Cloudant 数据库将会是整个项目架构的强大后盾。比如我们可以将 Cloudant 用于大规模动态实时数据的车联网项目,也可以将 Cloudant 用于基于大规模数据的文本分析项目中。Cloudant 以其强大的特性在当今非常流行的大数据云平台项目中占据着举足轻重的位置。

针对基于 Cloudant 的 Java 开发,目前存在着多套 Java library 供用户使用,比如 Java-Cloudant、ektorp 等等。但根据 Cloudant 的官方文档,Java-Cloudant 是 Cloudant 官方推荐的 Java library。因此,我们后面的所有有关 Java 示例详解的部分,都将采用 Java-Cloudant 作为我们的 Java library。当然,除了 Java-Cloudant 之外,我们还需要加入其它几个必须的依赖包,才可以进行基于 Java 的 Cloudant 开发。现将所有依赖列表如下:

1) Java-Cloudant

2) Commons Codec 1.6

3) Commons IO 2.4

4) Gson 2.2.4

5) OkHttp 2.5.0 (OPTIONAL)

除此之外,Cloudant 官方还对外提供其 RESTful API,而且令人愉快的是 Cloudant 和 Couch DB 有着相同的 REST API,因此它们有着相同的用法。之后我们会作出详细介绍。

Cloudant 数据库 Java 开发前期配置

首先,在这里我们进行 Cloudant 账户注册:https://Cloudant.com/sign-up/

图 1. Cloudant 账户注册

NoSQL 数据库 Cloudant

我们知道 Cloudant 本身是 document 型的 NoSQL 数据库,因此在 Cloudant 数据库中新建 document 是最为基本的操作,而又由于 Cloudant 基于 Couch DB,因此对于 document 的内容不难想象和其他 document 型的数据库一样,其数据的存储格式为 json 数据格式。

下面我们来举例说明:

首先,我们在新建的 Java project 中 src 目录下新建一个 config.properties 文件,之后我们将在这个文件里存储我们对于 Cloudant 数据库的访问权限:

图 2. config.properties 文件

NoSQL 数据库 Cloudant

在上图中,url 的前缀也就是用户注册时的用户名,登录 Cloudant 后,我们建立一个名为 Cloudanttest 的数据库, 这里注意一点,在建立数据库之后,为了安全起见,我们在之后的开发中并不使用注册时的用户名,即使它具有 admin 权限。因此我们需要新生成一个用户名,并且要对生成的用户名进行 admin 授权,如下:

图 3. 配置数据库

NoSQL 数据库 Cloudant

点击图 3 中的锁型配置按钮之后,我们就可以对数据库进行配置了。在下图 4 中,我们可以看到,里面有两个选项按钮,一个是 Generate API Key, 另一个是 Grant Rights。我们可以使用 Generate API Key 进行用户名的系统生成,也就是说点击这个按钮,之后 Cloudant 会自动给我们生成一组用户名及密码,然后我们可以点击最上面的 Admin 选项进行 admin 授权。当然,我们也可以通过在 GrantRights 中输入我们自定义的用户名,并进行授权,操作方法类似。

这里不难看出,新生成并授权过的 account,就是我们要在 config.properties 文件里配置的 access 信息了。

图 4. 新用户名生成并授权

NoSQL 数据库 Cloudant

接下来我们将生成的用户名密码配置在 config.properties 文件里,为安全起见,之后笔者都将使用 Cloudanttest 这个 mock up 出来的 account 进行讲解:

清单 1. config.properties 配置文件内容

CloudantNoSQLDB.credentials.url=https://Cloudanttest.Cloudant.com
CloudantNoSQLDB.credentials.username=atualtttdtdstopinundenos
CloudantNoSQLDB.credentials.password=passw0rd
db_name=Cloudanttest

有了 config 的配置信息,下面我们将引入两个 common 的类:

清单 2. ConfigProperties 类读取 config 信息

import Java.util.Properties;
public class ConfigProperties {
private static final String ENV_CONFIG_PROPERTIES = "/config.properties";
Properties properties = new Properties();
public ConfigProperties(String propertiesFile) {
try {
//load 配置文件信息
properties.load(ConfigProperties.class.getResourceAsStream(propertiesFile));
} catch (Exception e) {
e.printStackTrace();
}
}
public static ConfigProperties getEnvProperties(){
return new ConfigProperties(ENV_CONFIG_PROPERTIES);
}
public String getString(String key) {
try {
return (String) properties.get(key);
} catch (Exception e) {
e.printStackTrace();
return '!' + key + '!';
}
}
}

清单 3. ConfigurationUtil 获取 Cloudant 访问权限

public class ConfigurationUtil {
private static ConfigProperties envProperties = ConfigProperties.getEnvProperties();
private ConfigurationUtil() {
}
//获取环境变量,或从 properties 文件中获得配置信息
public static String getUserDefinedString(String name) {
String value = System.getenv(name);
return value != null && !value.isEmpty() ? value : envProperties.getString(name);
}
}

这个类是用来决定是否从 config 文件中获取 Cloudant 的访问权限。因为后期我们会介绍基于 Bluemix 的 Cloudant service 应用,这个 common 类也是基于这一点而存在的,原因是我们不仅可以从 config 文件中获取访问权限外,还可以从已绑定 Cloudant service 的 Bluemix app 的环境变量中获取 Cloudant 的访问权限。

至此,我们已经配置好了针对 Cloudant Java 开发所需的所有配置信息。

新建数据库文档

首先我们新建一个 Java Bean,包含了三个私有属性,name,age,address,以及它们所对应的 setter 和 getter 方法,如下:

清单 4. 新建 Java Bean

public class TestBean {
private String name;
private int age;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}

这是一个简单的 Java Bean,于是我们利用 Java 将这样一个 Bean 的对象存入 Cloudant DB:

清单 5. 存储 Java Bean 至 Cloudant 数据库

import com.Cloudant.client.api.CloudantClient;
import com.Cloudant.client.api.Database;
import com.ibm.fordme.common.config.ConfigurationUtil;
import com.ibm.fordme.common.constants.DBConstants;
public class CloudantTest{
public static void main(String[] args){
//数据库连接
String url = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_URL);
String username = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_USERNAME);
String password = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_PASSWORD);
CloudantClient client = new CloudantClient(url,username,password);
Database db = client.database(ConfigurationUtil.getUserDefinedString("db_name"), false);
//实例化 JavaBean
TestBean testBean = new TestBean();
testBean.setAddress("Xi'an");
testBean.setAge(26);
testBean.setName("yxy");
db.save(testBean);
}
}

其中 DBConstants类内容如下:

清单 6. 数据库配置

public class DBConstants {
public static final String CLOUDANT_URL = "CloudantNoSQLDB.credentials.url";
public static final String CLOUDANT_USERNAME = "CloudantNoSQLDB.credentials.username";
public static final String CLOUDANT_PASSWORD = "CloudantNoSQLDB.credentials.password";
}

从上面的代码不难看出,在存储信息到 Cloudant 之前,首先要获得三个前提条件,分别是 url,username,password。有了这三个条件,我们就可以通过 Java Cloudant lib 所提供的 API 来进行数据库连接,在上面的代码中,我们提供了数据库名,并且给第二个参数的位置赋值 false,这个布尔参数的意义是说是否在数据库不存在时新建这样一个数据库,也就是说如果将这个参数设置为 true 当然也是可以的,因为我们已经建好了库,因此我在这里将它设置为 false。 得到生成的 document 如下图所示:

图 5. 简单对象文档

NoSQL 数据库 Cloudant

在此我们发现生成的 document 里 Cloudant 会自动的帮我们生成另外两个字段:_id, _rev。

当然这都是 document 的唯一标识符,但是如果我们想自定义_id 的值的话,也是可以的,只需要在我们的 Java Bean 里增加_id 这个字段即可。

同时,Cloudant 也支持复杂对象的存储:

比如我们在 Test Bean 的基础上,再加上一个 TestBeanWrapper,如下

清单 7. 复杂对象存储

public class TestBeanWrapper {
private String wrapperName;
private TestBean data;
public String getWrapperName() {
return wrapperName;
}
public void setWrapperName(String wrapperName) {
this.wrapperName = wrapperName;
}
public TestBean getData() {
return data;
}
public void setData(TestBean data) {
this.data = data;
}
}

接着我们将这个 Wrapper 类的对象存入 Cloudant DB,获得下面的效果:

图 6. 复杂对象文档

NoSQL 数据库 Cloudant

至此,对于如何将信息写入 document 并存入 Cloudant DB,我想大家应该已经清楚了,下面我们将介绍如何在 Cloudant DB 中新建一个 View 并使用它。

创建视图

提到 View,大家肯定会感到非常熟悉,SQL DB 中 View 是很常用的,我们知道 View 本身就是虚表,而对于 NoSQL,比如 Cloudant,View 的意义其实和 SQL DB 中的 View 比较类似。对于 Cloudant DB 的 View,它可以有选择性地过滤 document,可以加快查找速度,在结果返回客户端之前可以被用来预处理结果。View 是简单的 Javascript 函数,它由两部分组成,map 函数和 reduce 函数,抛开晦涩的概念,下面我们举一个例子:

在之前建立好的数据库中,我们手动建立一个 View,如下:

清单 8. 简单视图定义

function (doc) {
if(doc._id){
emit(doc._id, doc.name);
}
}

它的意义是如果 document 存在_id 这个字段,那么将发射返回 document 的_id 以及 name 字段作为结果。

清单 9. 根据视图查询

import com.Cloudant.client.api.CloudantClient;
import com.Cloudant.client.api.Database;
import com.ibm.fordme.common.config.ConfigurationUtil;
import com.ibm.fordme.common.constants.DBConstants;
import Java.util.List;
import Java.util.Map;
public class CloudantTest {
public static void main(String[] args) {
String url = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_URL);
String username = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_USERNAME);
String password = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_PASSWORD);
CloudantClient client = new CloudantClient(url, username, password);
Database db = client.database(ConfigurationUtil.getUserDefinedString("db_name"), false);
//根据 View 查询数据库,并以 map 接收查询结果
List<Map> list = db.View("testView/new-view").query(Map.class);
System.out.println(list);
}
}

运行之后我们便可得到结果:

清单 10. 视图查询结果

[{id=43966a7a3b14452fb265b838bafa13ac, key=43966a7a3b14452fb265b838bafa13ac, value=yxy}, 
{id=5e85569056144a519fe724329d6e6708, key=5e85569056144a519fe724329d6e6708, value=null},
{id=7850ba5c69624fb98f6241015545acfe, key=7850ba5c69624fb98f6241015545acfe, value=null},
{id=d96380207b1d4b5fae5265f27d004218, key=d96380207b1d4b5fae5265f27d004218, value=null}]

从上面我们知道 View 本身并不难理解,而且很容易就可以根据自己的需要写出对应的 View 从而返回相应的结果。

新建索引

除了用 View 进行查询外,我们还可以用 index 进行查询,并且在大数据规模下,index 的查询效率要远高于 View。在 Cloudant 中 index 的建立,相对比较简单,我们在上面的代码中用一行就可以建好一个字段的 index,例如我们建立 wrapperName 这个字段的 index:

清单 11. Java 建立索引

db.createIndex("index_wrapper_name", "index_test", null,new IndexField[] { 
new IndexField("wrapperName", SortOrder.desc) });

那么我们就可以通过 index_wrapper_name 这个 index 用 wrapperName 这个字段进行 document 的相关查询。例如:

清单 12. 根据索引查询

import com.Cloudant.client.api.CloudantClient;
import com.Cloudant.client.api.Database;
import com.Cloudant.client.api.model.IndexField;
import com.Cloudant.client.api.model.IndexField.SortOrder;
import com.ibm.fordme.common.config.ConfigurationUtil;
import com.ibm.fordme.common.constants.DBConstants;
import com.ibm.ws.objectgrid.config.jaxb.deploymentPolicy.Map;
import Java.util.List;
public class CloudantTest {
public static void main(String[] args) {
String url = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_URL);
String username = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_USERNAME);
String password = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_PASSWORD);
CloudantClient client = new CloudantClient(url, username, password);
Database db = client.database(ConfigurationUtil.getUserDefinedString("db_name"), false);
//针对 wrapperName 字段建立 index
db.createIndex("index_wrapper_name", "index_test", null,
new IndexField[] { new IndexField("wrapperName", SortOrder.desc) });
//根据已建立 index 的字段,定义查询条件
String testIndex = "{/"wrapperName/":{ /"$gt/": null }}";
//用 map 接收查询结果
List<Map> map = db.findByIndex(testIndex, Map.class);
System.out.println(map);
}
}

在上面的代码中,由于我们建立了 wrapperName 这个字段的 index,因此我们就可以围绕这个字段定义查询条件,上面的代码中查询条件为 wrapperName 大于 null,也就是说只有 wrapperName 这个字段存在的 document 都会被返回。因此我们可以得到结果如下:

清单 13. 索引查询结果

[com.ibm.ws.objectgrid.config.jaxb.deploymentPolicy.Map@63d94f05, 
com.ibm.ws.objectgrid.config.jaxb.deploymentPolicy.Map@4fd30479,
com.ibm.ws.objectgrid.config.jaxb.deploymentPolicy.Map@26cacce0]

在这里我们要注意一点,在应用 index 时一定要谨慎,应当提前明确哪些字段需要建立index,哪些不需要。如果我们所处理的数据其规模呈动态增长,那么当数据规模特别大时,再去临时添加某个字段的 index 将会消耗不少时间。再者,对于 Cloudant,我们如果需要按照某个字段进行 document 排序,那么也必须针对这个字段提前建立好 index,否则将无法使用 sort 功能,这个我们在后面讲解 RESTful 调用时再述。

向 Cloudant 数据库上传附件

在 Cloudant 里,我们也可以上传文件作为附件进行存储。这里我们以上传图片来举例说明:

首先,我们定义一个 JavaBean,里面包含 name, createdBy, updatedBy 这三个属性。Image 类继承了 Cloudant 中的 Document 类,这样我们可以通过 document 的基本属性进行 attachment 的设置。

清单 14. 新建图片 JavaBean

import com.Cloudant.client.api.model.Document;
public class Image extends Document {
private String name;
private String created_by;
private String updated_by;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCreated_by() {
return created_by;
}
public void setCreated_by(String created_by) {
this.created_by = created_by;
}
public String getUpdated_by() {
return updated_by;
}
public void setUpdated_by(String updated_by) {
this.updated_by = updated_by;
}
}

接下来,我们定义一个类来进行图片到 base64 string 的转换。因为对于 Cloudant 的 attachment,它是通过 base64 string 来存储附件内容的。当然在这里,需要说明一点的是,我们这里只是为了本节示例的讲解而定义了这个类,今后为了方便我们也可以直接通过网络上的 base64 转换工具来得到图片的 base64 string。这里有一个应用场景,如果我们应用 RESTful 框架来进行项目开发,那么我们很清楚,这样做前后台是分离开的,那么对于图片上传,我们的后台 REST 接口将只关心前台传过来的 base64 string 而并不理睬这个 base64 string 是如何在前台通过图片转换得到的。这也就是我为什么说下面这个类的定义只是为了示例的讲解而存在的。

这里我们需要引入 apache 的 commons.codec 这个 jar 包。

清单 15. 图片-Base64 String 转换器

import org.apache.commons.codec.binary.Base64;
import Java.io.FileInputStream;
import Java.io.InputStream;
import Java.io.PrintStream;
public class Image2Base64 {
//将文件转为 base64 string
public String Base64String(String path) throws Exception {
InputStream in = new FileInputStream(path);
byte[] data = new byte[in.available()];
in.read(data);
in.close();
String encoder = Base64.encodeBase64String(data);
return encoder;
}
public InputStream Base64Stream(String path) throws Exception {
InputStream in = new FileInputStream(path);
return in;
}
public static void main(String[] args) throws Exception {
Image2Base64 test = new Image2Base64();
String result = test.Base64String("C:/Users/IBM_ADMIN/Desktop/IBM.png");
PrintStream out = new PrintStream("C:/Users/IBM_ADMIN/Desktop/IBMBase64Code.txt");
out.print(result);
out.close();
}
}

有了上面这个类,我们便可以将图片转换为 base64 string,于是我们定义下面这个类作为示例:

清单 16. 向 Cloudant 数据库上传图片

import com.Cloudant.client.api.CloudantClient;
import com.Cloudant.client.api.Database;
import com.Cloudant.client.api.model.Attachment;
import com.ibm.fordme.common.config.ConfigurationUtil;
import com.ibm.fordme.common.constants.DBConstants;
public class CloudantTest {
public static void main(String[] args) throws Exception {
String url = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_URL);
String username = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_USERNAME);
String password = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_PASSWORD);
CloudantClient client = new CloudantClient(url, username, password);
Database db = client.database(ConfigurationUtil.getUserDefinedString("db_name"), false);
Image image = new Image();
image.setId("image001");
image.setName("IBMLOGO");
image.setCreatedBy("xayangxy@cn.ibm.com");
Image2Base64Tool tool = new Image2Base64Tool();
//获得 base64 string
String base64Code = tool.getBase64String("C:/Users/IBM_ADMIN/Desktop/IBM.png");
//定义 attachment,用以存储文件生成的 base64 string
Attachment attachment = new Attachment();
attachment.setData(base64Code);
attachment.setContentType("image");
image.addAttachment("IBMLOGO", attachment);
//存储图片
db.save(image);
}
}

可以看到,上面的 code 非常好理解,前几行和上面的几个示例开端一样,是取得数据库的访问权限,之后我们实例化了一个 image 对象,并实例化了一个 attachment。可以看到,attachment 的内容需要用 base64 string 进行设置,进而我们还象征性的设置了图片的类型为“image”,而实际上 content type 的设置并无实际的意义,后面的过程想必大家都很熟悉,也就是我们第二节介绍的 create document to Cloudant,存储这个 Bean 到数据库中即完成了附件的上传功能。如下图所示:

图 7. Cloudant 文档附件展示

NoSQL 数据库 Cloudant

更新数据库文档

在 Cloudant 数据库中更新一个 document 其实和创建一个 document 是非常类似的,也很简单,这里我们基于上面的 image 示例举一个简单的例子,如下代码:

清单 17. 更新 Cloudant 数据库文档

import com.Cloudant.client.api.CloudantClient;
import com.Cloudant.client.api.Database;
import com.ibm.fordme.common.config.ConfigurationUtil;
import com.ibm.fordme.common.constants.DBConstants;
public class CloudantTest {
public static void main(String[] args) throws Exception {
String url = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_URL);
String username = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_USERNAME);
String password = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_PASSWORD);
CloudantClient client = new CloudantClient(url, username, password);
Database db = client.database(ConfigurationUtil.getUserDefinedString("db_name"), false);
String id = "image001";
Image image = db.find(Image.class, id);
image.setName("IBMLOGOSHOW");
db.update(image);
}
}

可以看到这个示例中体现本节内容的只有最后面的这三四行代码,不难理解,我们首先要知道需要被更新的 document 的 id,这里我们选择之前存储的 image001 这个 document,然后我们获取这个 document 所对应的 JavaBean,之后我们便可以通过 db.update 方法进行 document 更新了。这里需要注意的一个问题是,我们必须提前知道被更新 document 的 id 和其所对应 JavaBean 的类型。这个前提是无可厚非的,设想这样的一个场景,我们进行 RESTful 开发时,如果有 update 这样的一个 REST service,那么接口中必然要提供 id 这个参数作为输入项,而对于 document 所对应的 JavaBean,我们一般为了架构清晰,一个数据库中一般只存储一种类型的 document,例如我们有一个数据库叫 ibm_image,那么这个库中存储的所有 documents 都将是 Image 这个类型的 documents。因此我们在做更新操作时,需要转换的 JavaBean 类型是完全可知的。

删除数据库文档

对于删除操作,则更为简单。我们只需调用 db.remove 方法即可完成 delete document 操作。如下代码示例:

清单 18. 删除 Cloudant 数据库文档

import com.Cloudant.client.api.CloudantClient;

import com.Cloudant.client.api.Database;

import com.ibm.fordme.common.config.ConfigurationUtil;

import com.ibm.fordme.common.constants.DBConstants;

public class CloudantTest {

public static void main(String[] args) throws Exception {

String url = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_URL);

String username = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_USERNAME);

String password = ConfigurationUtil.getUserDefinedString(DBConstants.CLOUDANT_PASSWORD);

CloudantClient client = new CloudantClient(url, username, password);

Database db = client.database(ConfigurationUtil.getUserDefinedString("db_name"), false);

String id = "image001";

Image image = db.find(Image.class, id);

db.remove(image);

}

}

这里我们仍然需要知道 document 的 id,并通过 JavaBean 获取要删除的 document 所对应的对象,从而删除这个 document。当然也有另一个方法供使用者进行调用,即:

db.remove(String _id, String _rev)

这个方法需要知道 document 对应的_id 和_rev,才可进行删除操作,但这个方法本身笔者并不推荐使用。

基于 RESTful API 查询 Cloudant 数据库

Cloudant 为开发者提供了非常方便的 RESTful API,其中最重要的一个 API 即:xxx.Cloudant.com/dbname/_find,我们可以通过对此 API 的调用进行 document 查询:

图 8. 通过 selector 查询 Cloudant 数据库 NoSQL 数据库 Cloudant

这里我们使用了 chrome 浏览器自带的 REST Client 进行 rest 查询,Cloudant 支持 selector, sort, limit, skip, fields 这些 operators。上面的示例中我们就使用了 selector 这个 operator。前面说过如果要对某个字段进行 sort,则必须对此字段建立 index,这里我们举两个例子:

首先,当要进行 sort 的字段没有建立 index 时,如果进行 sort 操作,则会出现如下结果:

图 9. 无对应 index 时 sort 功能失效

NoSQL 数据库 Cloudant

可以清楚地看到,Cloudant 的 RESTful API 返回给我们了 sort 字段对应 index 不存在的错误提示。

接下来,我们利用前面 Create Index 章节讲解过的内容,对 name 字段建立 index,之后再次查询,根据 request body 里的查询条件,我们查询了,name 字段存在的所有 documents,并且按照 name 字段进行降序排序,得到结果如下:

图 10. 有对应 index 时基于 sort 功能查询文档 NoSQL 数据库 Cloudant

这里我们还可以设置分页功能,利用 limit 和 skip,比如我们要查询 sort 之后中间两条数据,那么我们可以利用下面的查询条件:

清单 19. 查询条件

{"selector":{"name":{"$gt":null}}, "sort":[{"name":"desc"}], "limit":2, "skip":1}

这样我们就可以得到想要的结果了。

Cloudant 数据库复制

图 11. Cloudant 数据库复制入口

NoSQL 数据库 Cloudant

对 Cloudant 数据库进行 replicate,我们登录 Cloudant 后,在要 replicate 的数据库右侧点击 replicate button,之后我们可以看到如下界面:

图 12. Cloudant 本地数据库复制

NoSQL 数据库 Cloudant

输入 replicate 的新数据库名,点击 replicate 即可。除了上面的本地模式外,我们也可以 replicate 远程数据库,换句话说,就是我们可以将另一个 Cloudant 账户中的某一个数据库 replicate 到当前 Cloudant 账户中。与本地模式所不同的是我们在 source database 选项中要填入规定样式

(https://$USERNAME:$PASSWORD@$REMOTE_USERNAME.Cloudant.com/$DATABASE_NAME)的访问权限,如下所示:

图 13. Cloudant 远程数据库复制

NoSQL 数据库 Cloudant

不难看出,Cloudant 中对数据库的 replicate 操作还是非常简单方便的。

基于 Bluemix 平台的 Cloudant service 应用

对于 Bluemix,想必大家都有所耳闻,在本文章中不作详述,有兴趣可以参考 developerWorks 上的相关文章,或参考 Bluemix 的官方文档。我们知道,Bluemix 是 IBM 公司最具有代表性的云平台,基于 Bluemix 我们可以方便的创建自己的工程,并为工程绑定需要的 service,这对用户来说是非常方便的。当然,Cloudant 也是 Bluemix 可绑定的 service 之一。下面我们将讲述基于 Bluemix 平台的 Cloudant service 应用。

登录 Bluemix,我们新建一个 Liberty for Java 的 app:

图 14. 基于 Bluemix 的 Cloudant service 界面

NoSQL 数据库 Cloudant

接着我们点击”ADD A SERVICE OR API”, 在 Data and Analytics 目录下找到 Cloudant service,如下:

图 15. 基于 Bluemix 新建 Cloudant 服务

NoSQL 数据库 Cloudant

点击后进入 create service 页面,我们便可以 create 一个新的 Cloudant service 并绑定给之前新建的 APP,如图所示:

图 16. 基于 Bluemix 的 Cloudant service 信息配置

NoSQL 数据库 Cloudant

点击 create,之后我们便得到了绑定好了 Cloudant service 的 Java liberty app:

图 17. 基于 Bluemix 的 Cloudant service 建立完成

NoSQL 数据库 Cloudant

进入 app,我们可以在左侧找到环境变量这个选项,点击进入后,我们发现已经绑定的 Cloudant service 的所有权限信息被列在 VCAP 环境变量里,如下所示:

图 18. 基于 Bluemix 的 Cloudant service 环境变量 NoSQL 数据库 Cloudant

这里我们回想一开始在“Cloudant 数据库 Java 开发前期配置”章节中提到过的,ConfigurationUtil 类中我们可以调用 getUserDefinedString 这个方法,也就是说它可以从 user defined 环境变量里读取访问权限信息,如下:

图 19. 基于 Bluemix 的 Cloudant service 自定义环境变量

NoSQL 数据库 Cloudant

此时我们所示的内容是之前我们手工建立的 Cloudant 数据库的访问权限。在这里我们可以将其内容换为上面绑定后环境变量中的访问权限。其实这里,Bluemix 本身就有 VCAP 环境变量,我们绑定了 Cloudant service 之后,其实可以通过 Java API 接取得环境变量中的所有信息,因此我们可以对 ConfigurationUtil 这个类有下面的扩展:

图 20. 基于 Bluemix 的 Cloudant service 方法扩展

NoSQL 数据库 Cloudant

可以看到我们定义了 getVcapString 这个方法,因此这个方法的逻辑是,首先访问 vcap 环境变量,如果没有找到,则调用 getUserDefinedString 去 user defined 环境变量中进行查找,如果仍然未找到访问权限信息,则去 src 目录下访问 config.properties 文件获取访问信息。

Cloudant RESTful API 详解

在之前我们介绍了 Cloudant RESTful API query 部分的用法,也介绍了基于 Bluemix 平台的 Cloudant service 应用。在这一节,我们将结合这两个方面进行讲解。 为方便理解,其它配置的过程笔者不再进行赘述,下面我们将基于一个已经实现好的 RESTful 架构的 liberty Java app 进行 RESTful API 的调用讲解。如图:

图 21. 基于 Bluemix service 的 RESTful API 查询

NoSQL 数据库 Cloudant

在这里我们已经在 Bluemix 上部署好了 RESTful app,因此上面的 URL 中即为我们 RESTful services 中的其中一个,这里我们用典型的查询操作来进行讲解。可以看到上面的 http 请求中 request body 设置了查询条件,即

1)document 存在

2)按 name 进行降序排列

3)只输出 name 和 theme 这两个字段

4)只输出三条记录,于是我们可以得到如下结果:

图 22. 基于 Bluemix service 的 RESTful API 查询结果 NoSQL 数据库 Cloudant

有了这样的后台操作,对于已被剥离开的前台来说,开发者可以很方便很清晰的进行前台开发以及和后台的数据交互。

除此之外,我们再给出一个复杂类型 document 的查询示例: 例如 document 的内容如

清单 20. 复杂 Json 文件示例

{
"_id": "100000000000000000",
"_rev": "1-67d924da9402c2f6d70a229f91bfe0e5",
"geometry": {
"type": "Point"
},
"properties": {
"mmTrajectory": {
"altitude": 0,
"heading": 0,
"latitude": 0,
"longitude": 0,
"moID": "1446118616715166666",
"note": "By-passed because of missing information or point is not in a known region",
"speed": 1.314919948566666,
"tripID": "",
"ts": 1452071840
},
"packet": {
"meta": {
"account": "test",
"event": "track"
},
"payload": {
"asset": "1446118616715171320",
"connection_id": 795943814836519000,
"connection_id_str": "795943814836519042",
"fields": {
"MDI_CUSTOM_PID_27": {
"b64_value": "ODAuOQ=="
},
"MDI_CUSTOM_PID_49": {
"b64_value": "Mi4xODQzMTQ1NDQzNjM0MjM3RTE4"
},
"MDI_CUSTOM_PID_50": {
"b64_value": "MS4xMzU5NDQ4Mjc3MDA1MTA3RTE3"
},
"MDI_CUSTOM_PID_7": {
"b64_value": "NTYuNDcwNTg4MjM1Mjk0MTE2"
},
"MDI_CUSTOM_PID_8": {
"b64_value": "My4w"
}
},
"id": 795944156529666666,
"id_str": "795944156529666666",
"index": 1110,
"received_at": "2016-01-06T09:17:54Z",
"recorded_at": "2016-01-06T09:17:20Z",
"recorded_at_ms": "2016-01-06T09:17:20.000Z",
"streams_received_at": "2016-01-06T09:17:55Z"
}
},
"vehicleId": "1446118616715666666",
"pid_fields": {
"charge_level": {
"value": "56.470588235294116",
"tip": "56.470588235294116"
},
"charge_status": {
"value": "3",
"tip": "3"
},
"charge_range": {
"value": 80.9,
"tip": "80.9"
}
},
"pidop_processed_at": "2016-01-06T09:18:01Z"
},
"type": "feature"
}

如果我们要对其中的某些字段进行条件查询,则在 http 请求时 request body 中的字段必须精确到 其绝对路径,例如针对上面的 document,我们用下面的语句进行查询:

清单 21. Cloudant 数据库 RESTful API 复杂查询语句

{
"selector":
{
"properties.packet.payload.asset":"1446118616715171320","properties.packet.payload.recorded_at":{
"$gte":"2015-11-01T00:00:00Z"
},"
properties.pid_fields.charge_level":{"$exists":true}}, 
"sort":[{"properties.packet.payload.asset":"desc"},{"properties.packet.payload.recorded_at":"desc"}], 
"limit":2
}

可以看到,像 charge_level,asset 等等这样的字段,都精确到了具体的路径,比如:properties.packet.payload.asset 和 properties.pid_fields.charge_level。只有这样我们才可以成功的查询出想要的结果。

Cloudant 应用注意事项

Cloudant 的优点想必大家已有体会,这里不必多说,在此笔者总结两点使用 Cloudant 时需要注意的地方:

1)sort 功能的使用是有限制的,当我们想进行多级 sort 查询时,例如 document 中有 a,b,c,d 四个字段,如果我们想对 a,b,c 同时进行排序,则必须存在一个 a,b,c 绑定在一起的 index,而且需要同序定义,即都是 desc 或都是 asc,即需要创建这样的 index:

清单 22. 为多级排序建立复合索引

db.createIndex("index_name_field", "indexes", null, new IndexField[] 
{
 new IndexField("a", SortOrder.desc),new IndexField("b",
 SortOrder.desc),new IndexField("c", SortOrder.desc)
 }
);

并且至少要保证有一个 sort 的字段要存在于 selector 中。

2)selector 是查询 Cloudant RESTful API 时必须的 operator,当然用户可以根据需要进行扩展,让自己基于 Cloudant 数据库的 RESTful service 没有这样的限制。

结束语

Cloudant 是 IBM 公司非常具有代表性的产品之一,我们知道在云平台大数据盛行的当下,海量规模数据的存储无疑是人们最关心的问题。Cloudant 数据库以其自身强大的处理大规模数据、支持高并发行访问的优势受到了业界高度的认可。本文系统地介绍了 Cloudant 数据库的几乎所有基本操作,希望文中提到的概念和示例代码对读者的开发工作提供一定的帮助。

原文  http://www.uml.org.cn/sjjm/2016081605.asp
正文到此结束
Loading...