转载

RESTful 项目中对 Cloudant 服务的封装使用

搭建 RESTful Web 项目

因为 Cloudant 是用的 REST 设计,所以需要先在 Eclipse 上搭建一个 RESTful 的 Web 项目,然后上传到 Bluemix 上。我们使用的是 Java 语言来搭建一个 REST 框架的 Web 项目。Java EE6 引入了 JAX-RS 的标准,而 IBM 的 Liberty Profile 自带的包支持 JAX-RS,所以我们先介绍如何安装配置 Liberty Profile 和搭建 RESTful 的 web 项目。

在 Eclipse 上安装 Liberty Profile Tools

打开 Eclipse, 点击进入 Help -> Eclipse Marketplace,然后搜索 Liberty Profile

图 1.Eclipse Marketplace 搜索界面

RESTful 项目中对 Cloudant 服务的封装使用

根据本地 Eclipse 的版本选择下载,这里我们下载的是对应的 Luna 版的。

图 2.搜索 Liberty Profile Tools 结果

RESTful 项目中对 Cloudant 服务的封装使用

点击选择 Next

图 3.安装 Liberty Profile Tools

RESTful 项目中对 Cloudant 服务的封装使用

点击 Accept 并完成对应版本 Liberty Profile 的安装

配置 Liberty Profile

进入 Preferences -> Server -> Runtime Environments,选择 Add

图 4.Eclipse Preference 界面

RESTful 项目中对 Cloudant 服务的封装使用

选择 IBM 下的 WebSphere Application Server V8.5 Liberty Profile

图 5.Eclipse 创建 New Server Runtime

RESTful 项目中对 Cloudant 服务的封装使用

指定 Liberty Profile 的 Path,如果没有下载 Liberty Profile,点击 download or install 安装 Liberty Profile

图 6.配置 Liberty Profile Runtime Environment

RESTful 项目中对 Cloudant 服务的封装使用

完成配置后,可以看到已有 Liberty 的 Runtime

图 7.Eclipse Preference 的 Runtime Environment

RESTful 项目中对 Cloudant 服务的封装使用

创建动态 Web 项目

进入 File -> New -> Dynamic Web Project 创建动态 Web 项目

图 8.创建动态 Web 项目

RESTful 项目中对 Cloudant 服务的封装使用

点击 Modify,进入需要勾选 JAX-RS 并确保 Runtime 是 Liberty Profile

图 9.配置 runtime

RESTful 项目中对 Cloudant 服务的封装使用

一直 Next 至最后选择 JAX-RS Implementation Library,选择 IBM WebSphere Application Server Liberty JAX-RS Library.

图 10.IBM WebSphere Application Server Liberty JAX-RS Library

RESTful 项目中对 Cloudant 服务的封装使用

创建 RESTful 的 Web Service

清单 1. 创建 Service 类

package libertydemo; import java.io.File; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Response; @Path("") publicclass Service { @GET @Path("/test") public String testGet(){ return "OK"; } }

创建 Application

目前 JAX-RS 支持注解方式创建 Application,首先新建一个类 MyApplication 继承 Application。

清单 2. MyApplication 类

package libertydemo; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/rest") publicclass MyApplication extends Application{ }

至此,我们已经搭建好一个 RESTful 的 Web 项目,具体的 Service 内容,会在下面的内容介绍。

回页首

创建 Cloudant 数据库并添加文档

Cloudant 是 IBM 的一个 DBaaS 数据库即服务软件产品。它是基于 Apache 的 CouchDb 项目和开源项目 BigCouch 项目的 NoSQL 分布式数据库。Cloudant 数据库被设计成为一个 RESTful 的 web service, 提供了数据管理功能。IBM 的 Bluemix 平台集成了 Cloudant 服务。当在 Bluemix 上需要使用 Cloudant 时,创建一个 Cloudant 服务,并把这个服务绑定到应用上,就可以直接使用 Cloudant 数据库。

创建 Bluemix 的应用

首先登入 Bluemix 账号,创建一个 Runtimes 服务,选择 Liberty for Java。

图 11.创建 Liberty for Java

RESTful 项目中对 Cloudant 服务的封装使用

输入 App 的名称为 IBMCloudantApp, Host 为 IBMCloudantApp.mybulemix.net,在 Selected Plan 下拉菜单中选择 Default。

图 12.创建 IBMCloudantApp 应用

RESTful 项目中对 Cloudant 服务的封装使用

验证一下该应用是否被成功创建,在浏览器中输入 http://ibmcloudantapp.mybluemix.net ,可以看到 Hi world 的欢迎页面。

添加 Cloudant 服务

首先在 CATELOG 导航栏下的 Data Management 中,选择 Cloudant NoSQL DB。

图 13.选择 Cloudant NoSQL DB 服务

RESTful 项目中对 Cloudant 服务的封装使用

在添加 Cloudant 页面中选择需要使用 Cloudant 服务的 App,输入 Service name 为 Cloudantdb。

图 14.创建 Cloudantdb 服务

RESTful 项目中对 Cloudant 服务的封装使用

创建了 Cloudant NoSQL DB 服务之后,会弹出一个对话框,提示你需要重新部署一下原创建的 APP,点击确定。

选择页面左边的导航条目中的 Liberty for Java. 显示已创建的 APP 实例的详细信息。在 Environment Variables 中列出了 Cloudant 数据库服务的详细信息:

图 15.Cloudant 数据库信息

RESTful 项目中对 Cloudant 服务的封装使用

可以看到 Cloudant 数据库列出了下面几项环境变量:

表 1.Cloudant 数据库变量说明

变量名称 变量意义
name 用户命名的数据库实例名称
host Cloudant 服务的主机号
port Cloudant 服务的端口号
usename 认证的用户名
password 认证的用户密码
url Cloudant 数据库访问的 url 地址

创建 Cloudant 数据库

到目前为止,你已经在 Bluemix 上创建了一个基于 liberty profile 的项目,并创建了一个 Cloudant NoSQL 的服务。接下来的问题是如何去管理这个 Cloudant 数据库。Cloudant 提供了一个 web 的 Dashboard,你可以在这个 Dashboard 上面管理你创建的数据库。选择页面左边导航条目中的 SERVICES 下的 Cloudant NoSQL DB 点击右上角的 Launch 按钮。

在 Database 页面的左上角点击 Add New Database,输入 crud 数据库名称,点击 Create 按钮。

图 16.创建 crud 数据库实例

RESTful 项目中对 Cloudant 服务的封装使用

选择 crud 数据库,点击页面左边 All Documents 的加号,创建一个新的文档,如下图 17 所示。

图 17.创建一个文档

RESTful 项目中对 Cloudant 服务的封装使用

回页首

Cloudant 服务使用配置

  1. 在本项目中,前端采用 jquery.couch.js 的 jQuery 库,对 Cloudant 数据库进行操作,项目中需要把 jquery-2.1.1.js 和 jquer.couch.js 两个 js 文件引入。后端使用了 Apache http jar 包实现 HTTP 请求的转发,需要引入四个 jar 包:commons-codec-1.6.jar、commons-logging-1.1.3.jar、httpclient-4.3.6.jar、httpcore-4.3.3.jar。
  2. 项目中使用读取 properties 文件中的三个属性值:dbhost,username,password。其中 dbhost 表示 Cloudant 数据库的 url,username 和 password 分别是访问数据库的用户名和密码。使用 Properties 的 load 方法读取文件中配置的 Cloudant 数据库属性。
  3. 初始化 CloseableHttpClient 对象,初始化一个 SSL 链接的可信任客户端 client.

清单 3.初始化 client

public static CloseableHttpClient getClient() {   CredentialsProvider provider = new BasicCredentialsProvider();   UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(       username, password);   provider.setCredentials(AuthScope.ANY, credentials);   SSLContextBuilder builder = new SSLContextBuilder();   try {     builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());   } catch (NoSuchAlgorithmException e) {     // TODO Auto-generated catch block     e.printStackTrace();   } catch (KeyStoreException e) {     // TODO Auto-generated catch block     e.printStackTrace();   }   SSLConnectionSocketFactory sslsf = null;   try {     sslsf = new SSLConnectionSocketFactory(builder.build(),         SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);   } catch (KeyManagementException e) {     e.printStackTrace();   } catch (NoSuchAlgorithmException e) {     e.printStackTrace();   }   return HttpClients.custom().setSSLSocketFactory(sslsf)       .setDefaultCredentialsProvider(provider).build(); } 

回页首

服务器端对 Cloudant 基本操作的封装

Cloudant 数据库提供的 API 不能直接去访问 cloudant.com 域名,因为你的 web 项目是部署在自己的服务器上的,如果你直接去访问 cloudant.com 域名,就会造成跨域访问,浏览器对跨域有安全上的限制。虽然可以通过 JSONP 的方式,获取跨域的资源,但只能实现 HTTP 的 GET 请求,不能满足你访问 Cloudant 数据库的所有操作。因此在 web 项目中需要将从页面上的 Cloudant 请求在服务器端进行封装,封装后访问 Cloudant 的 API,再把从 Cloudant 获取的结果数据返回给浏览器。这就实现了 web 项目通过页面访问 Cloudant 数据库。

本节介绍如何对 Cloudant 基本操作进行分装,主要包括增删改查(CRUD)。在 Cloudant 中存储的文件格式都是 JSON 格式。每一个文档必须要有一个_id 键值和一个_rev 键值,其中_id 表示文档唯一的 id 值,_rev 表示该文档的版本号。

Cloudant 的 API 为如下信息:

表 2.Cloudant 数据库基本操作的 API

HTTP请求方式 URL 路径 描述
POST /db 创建一个新的文档
GET /db/doc 返回最新版本的文档
PUT /db/doc 插入一个新的文档,或者修改已经存在文档
DELETE /db/doc 删除一个已经存在的文档

如何增加一个 JSON 格式文档,根据 Cloudant 提供的 API,可以手动指定该文档的 id,如果不指定文档 id,那么 id 会由 Cloudant 自动产生。

首先设置 web 服务器的访问访问 Cloudant 数据库路径,指定对应的访问数据库,初始化一个 JSON 格式的文档变量,调用 saveDoc 函数,通过 POST 方法把 doc 发送到 web 服务器上。

清单 4.浏览器端创建文档

$('#create').click(function() {   var doc = $.parseJSON('{"title":"HTML and CSS: Design and Build Websites",'   +'"price":20,"authors":["Jon Duckett"],"language":"html"}');   $.couch.urlPrefix = '../jaxrs/cloudant';   var db = $.couch.db('crud');   db.saveDoc(doc,{   success : function(response, textStatus, jqXHR) {   console.log('success');   // do something if the save works   },   error : function(jqXHR, textStatus, errorThrown) {   // do something else if it goes wrong   console.log('error');   }   });    });

设置 cloudant.com 的路径,获取初始化后 CloseableHttpResponse 对象,把浏览器端传过来的 JSON 对象转化为 StringEntity 对象,再把其放入 HttpPost,并调用 execute 函数,把返回的 httpresponse 转为 JSON 格式封装后的 response。

清单 5.服务器段创建文档

@POST @Produces(value = MediaType.APPLICATION_JSON) @Path(value = "/{db}") public Response SaveDoc(@PathParam("db") String db, JSONObject json) {  HttpPost httpPost = new HttpPost(cloudantURL + db);  System.out.println(cloudantURL + db);  CloseableHttpResponse response = null;  CloseableHttpClient client = getClient();  try {  StringEntity entity = new StringEntity(json.toString());  entity.setContentType("application/json");  httpPost.setEntity(entity);  httpPost.setHeader("Connection", "keep-alive");  response = client.execute(httpPost);  HttpEntity ReEntity = response.getEntity();  String responseString = EntityUtils.toString(ReEntity, "UTF-8");  int status = response.getStatusLine().getStatusCode();  Header[] headers = response.getAllHeaders();  client.close();  return httpResponse2JsonRespone(status, responseString, headers);  } catch (UnsupportedEncodingException e1) {  // TODO Auto-generated catch block  e1.printStackTrace();  } catch (ClientProtocolException e) {  // TODO Auto-generated catch block  e.printStackTrace();  } catch (IOException e) {  // TODO Auto-generated catch block  e.printStackTrace();  }  return Response.serverError().build();   }

查找一个文档,这里使用一个最简单方式,通过文档的 id 来查找对应的文档。

先设置 web 服务器的访问 Cloudant 数据库路径,指定对应的访问数据库,并调用 openDoc 方法,传入对应的文档 id 和请求返回之后的回调函数。

清单 6.浏览器查找文档

$('#read').click(function() {     $.couch.urlPrefix = '../jaxrs/cloudant';   var db = $.couch.db('crud');   db.openDoc(id,{    success:function(data){     $('#read').innerhtml     console.log(data);    }   });  });

设置 cloudant.com 的路径,获取初始化后 CloseableHttpResponse 对象,把浏览器端传过来 JSON 对象转化为 StringEntity 对象,再把其放入 HttpPost,并调用 execute 函数。把返回的 httpresponse 转为 JSON 格式封装后的 response。

清单 7.服务器端查找文档

@GET @Produces(value = MediaType.APPLICATION_JSON) @Path(value = "/{db}/{docid}") public Response getDocById(@PathParam("db") String db,       @PathParam("docid") String docid) {     HttpGet httpGet = new HttpGet(cloudantURL + db + "/" + docid);     System.out.println(cloudantURL + db + "/" + docid);     HttpResponse response = null;     CloseableHttpClient client = getClient();     try {       response = client.execute(httpGet);       HttpEntity entity = response.getEntity();       String responseString = EntityUtils.toString(entity, "UTF-8");       System.out.println(responseString);       int status = response.getStatusLine().getStatusCode();       Header[] headers = response.getAllHeaders();       client.close();       return httpResponse2JsonRespone(status, responseString, headers);     } catch (ClientProtocolException e) {       // TODO Auto-generated catch block       e.printStackTrace();     } catch (IOException e) {       // TODO Auto-generated catch block       e.printStackTrace();     }     return Response.serverError().build();   }

更新一个文档,首先设置 web 服务器的访问访问 Cloudant 数据库路径,指定对应的访问数据库,初始化一个 JSON 格式的文档变量,必须要有_id 和_rev 键值,设置请求 header 的 If-Match 的值为浏览器的请求参数 rev,调用 saveDoc 函数,通过 POST 方法把 doc 发送到 web 服务器上。

清单 8.浏览修改文档

$('#update').click(function() {       var doc = $.parseJSON('{"_id":"id","_rev":"rev","title":"HTML and CSS: Design' +'and Build Websites","price":100,"authors":["Jon Duckett"],"language":"html"}');       $.couch.urlPrefix = '../jaxrs/cloudant';       var db = $.couch.db('crud');       db.saveDoc(doc, {         success : function(response, textStatus, jqXHR) {         console.log('success');         // do something if the save works         },         error : function(jqXHR, textStatus, errorThrown) {         // do something else if it goes wrong         console.log('error');         }       });    });

设置 cloudant.com 的路径,获取初始化后 CloseableHttpResponse 对象,把浏览器端传过来 JSON 对象转化为 StringEntity 对象,再把其放入 HttpPost,如果 rev 的值存在,设置请求 header 的 If-Match 的值为浏览器的请求参数 rev,如果不存在 rev,那么该操作是带 id 的新建一个文档。并调用 excute 函数。版返回的 httpresponse 转为为 JSON 的封装后的 response。

清单 9.服务器端修改文档

@PUT  @Produces(value = MediaType.APPLICATION_JSON)  @Path(value = "/{db}/{id}")  public Response SaveDocById(@PathParam("db") String db, @QueryParam("rev") String rev, JSONObject json) { String responseString = null; HttpPut httpPut = new HttpPut(cloudantURL + db); System.out.println(cloudantURL + db);  CloseableHttpResponse response = null; CloseableHttpClient client = getClient(); try { StringEntity entity = new StringEntity(json.toString()); entity.setContentType("application/json"); httpPut.setEntity(entity); httpPut.setHeader("Connection", "keep-alive"); if(null != rev && !"".equals(rev)){ httpPut.setHeader("If-Match", rev); } response = client.execute(httpPut); HttpEntity ReEntity = response.getEntity(); responseString = EntityUtils.toString(ReEntity, "UTF-8"); int status = response.getStatusLine().getStatusCode(); Header[] headers = response.getAllHeaders(); client.close(); return httpResponse2JsonRespone(status, responseString, headers); } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(responseString); return Response.serverError().build();;  } 

删除一个文档,首先设置 web 服务器的访问访问 Cloudant 数据库路径,指定对应的访问数据库,对应文档的_id 和_rev 的值,并转为 Json 格式的。调用 revomeDoc 方法传入 doc 和请求返回之后的回调函数。

清单 10.浏览器端删除文档

$('#delete').click(function() {  var doc =$.parseJSON('{"_id": id,"_rev":rev}');  $.couch.urlPrefix = '../jaxrs/cloudant';  var db = $.couch.db('crud');  db.removeDoc(doc, {  success : function(response, textStatus, jqXHR) {  console.log(response);  },  error : function(jqXHR, textStatus, errorThrown) {  // do something else if it goes wrong  }  });  });

设置 cloudant.com 的路径,获取初始化后 CloseableHttpResponse 对象,把浏览器端传过来 JSON 对象转化为 StringEntity 对象,再把其放入 HttpPost,设置请求 header 的 If-Match 的值为浏览器的请求参数 rev,并调用 excute 函数。把返回的 httpresponse 转为 JSON 封装后的 response。

清单 11.服务器端删除文档

@DELETE @Produces(value = MediaType.APPLICATION_JSON) @Path(value = "/{db}/{docid}") public Response deleteDocById(@PathParam("db") String db,     @PathParam("docid") String docid, @QueryParam("rev") String rev) {   HttpDelete httpDelete = new HttpDelete(cloudantURL + db + "/" + docid);   System.out.println(cloudantURL + db + "/" + docid);   httpDelete.setHeader("X-SyncTimeOut", "300");   httpDelete.setHeader("If-Match", rev);   HttpResponse response = null;   CloseableHttpClient client = getClient();   try {     response = client.execute(httpDelete);     HttpEntity ReEntity = response.getEntity();     String responseString = EntityUtils.toString(ReEntity, "UTF-8");     int status = response.getStatusLine().getStatusCode();     Header[] headers = response.getAllHeaders();     client.close();     return httpResponse2JsonRespone(status, responseString, headers);   } catch (ClientProtocolException e) {     // TODO Auto-generated catch block     e.printStackTrace();   } catch (IOException e) {     // TODO Auto-generated catch block     e.printStackTrace();   }   return null; } 

回页首

封装 Cloudant 以支持高级查询的可能性

前面讨论了对 Cloudant 数据库 CRUD 基本操作的封装,在这里我们对在 Cloudant 数据库实现高级查询的可能做了初步讨论。

所谓高级查询,是根据关系型数据库的定义,常见的高级查询有:模糊查询,聚合查询,排序函数,联合查询,连接查询,子查询。

在 Cloudant 数据库中,因为没有表的概念,Cloudant 的查询都是对所有的 document 进行操作,于是也就没有联合查询、连接查询和子查询。对于其他高级查询,Cloudant 提供了 3 种方式来实现,分别为 Index,Map/Reduce 和 Search。通过 Cloudant 提供的这些方式,可以尝试实现传统的高级数据库查询。

我们现在举例说明,如何通过 Map/Reduce 来实现模糊查询。

假设要查询包含 java 关键字书籍的名字,在关系型数据库里面,我们用的 SQL 语句是这样:

“SELECT id, name, address FROM DOC WHERE type LIKE ‘%book%’”

而在 Cloudant 当中,也可以对应的实现,我们指需要在 Map 函数中,加上正则表达式即可。

清单 12. Cloudant 的模糊查询

function(doc) {   if (new RegExp(".*book.*").test(doc.Type)) {  emit(doc._id, {LastName: doc.LastName,   FirstName: doc.FirstName, Address: doc.Address});   } }

回页首

结束语

非关系型数据库随着互联网 Web2.0 的兴起得到了非常迅速的发展,Cloudant 作为 NoSQL 的杰出代表必然会得到越来越多的运用。Bluemix 作为 IBM 的云端 PaaS 平台,将会有更多的开发者在上面进行软件项目的开发部署。本文提供了可配置的方式对部署在 Bluemix 的 Cloudant 的服务进行了对数据库基本 CRUD 操作的封装,成功的解决了浏览器跨域访问问题,让用户在操作云端数据库时有传统数据库的使用体验。开发者可以在实际使用中借鉴或者直接使用我们的封装方法,轻松实现对 Cloudant 服务的调用,我们还进一步的讨论封装 Cloudant 支持高级查询的可能。

正文到此结束
Loading...