因为 Cloudant 是用的 REST 设计,所以需要先在 Eclipse 上搭建一个 RESTful 的 Web 项目,然后上传到 Bluemix 上。我们使用的是 Java 语言来搭建一个 REST 框架的 Web 项目。Java EE6 引入了 JAX-RS 的标准,而 IBM 的 Liberty Profile 自带的包支持 JAX-RS,所以我们先介绍如何安装配置 Liberty Profile 和搭建 RESTful 的 web 项目。
打开 Eclipse, 点击进入 Help -> Eclipse Marketplace,然后搜索 Liberty Profile
图 1.Eclipse Marketplace 搜索界面
根据本地 Eclipse 的版本选择下载,这里我们下载的是对应的 Luna 版的。
图 2.搜索 Liberty Profile Tools 结果
点击选择 Next
图 3.安装 Liberty Profile Tools
点击 Accept 并完成对应版本 Liberty Profile 的安装
进入 Preferences -> Server -> Runtime Environments,选择 Add
图 4.Eclipse Preference 界面
选择 IBM 下的 WebSphere Application Server V8.5 Liberty Profile
图 5.Eclipse 创建 New Server Runtime
指定 Liberty Profile 的 Path,如果没有下载 Liberty Profile,点击 download or install 安装 Liberty Profile
图 6.配置 Liberty Profile Runtime Environment
完成配置后,可以看到已有 Liberty 的 Runtime
图 7.Eclipse Preference 的 Runtime Environment
进入 File -> New -> Dynamic Web Project 创建动态 Web 项目
图 8.创建动态 Web 项目
点击 Modify,进入需要勾选 JAX-RS 并确保 Runtime 是 Liberty Profile
图 9.配置 runtime
一直 Next 至最后选择 JAX-RS Implementation Library,选择 IBM WebSphere Application Server Liberty JAX-RS Library.
图 10.IBM WebSphere Application Server Liberty JAX-RS Library
清单 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"; } }
目前 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 是 IBM 的一个 DBaaS 数据库即服务软件产品。它是基于 Apache 的 CouchDb 项目和开源项目 BigCouch 项目的 NoSQL 分布式数据库。Cloudant 数据库被设计成为一个 RESTful 的 web service, 提供了数据管理功能。IBM 的 Bluemix 平台集成了 Cloudant 服务。当在 Bluemix 上需要使用 Cloudant 时,创建一个 Cloudant 服务,并把这个服务绑定到应用上,就可以直接使用 Cloudant 数据库。
首先登入 Bluemix 账号,创建一个 Runtimes 服务,选择 Liberty for Java。
图 11.创建 Liberty for Java
输入 App 的名称为 IBMCloudantApp, Host 为 IBMCloudantApp.mybulemix.net,在 Selected Plan 下拉菜单中选择 Default。
图 12.创建 IBMCloudantApp 应用
验证一下该应用是否被成功创建,在浏览器中输入 http://ibmcloudantapp.mybluemix.net ,可以看到 Hi world 的欢迎页面。
首先在 CATELOG 导航栏下的 Data Management 中,选择 Cloudant NoSQL DB。
图 13.选择 Cloudant NoSQL DB 服务
在添加 Cloudant 页面中选择需要使用 Cloudant 服务的 App,输入 Service name 为 Cloudantdb。
图 14.创建 Cloudantdb 服务
创建了 Cloudant NoSQL DB 服务之后,会弹出一个对话框,提示你需要重新部署一下原创建的 APP,点击确定。
选择页面左边的导航条目中的 Liberty for Java. 显示已创建的 APP 实例的详细信息。在 Environment Variables 中列出了 Cloudant 数据库服务的详细信息:
图 15.Cloudant 数据库信息
可以看到 Cloudant 数据库列出了下面几项环境变量:
表 1.Cloudant 数据库变量说明
变量名称 | 变量意义 |
---|---|
name | 用户命名的数据库实例名称 |
host | Cloudant 服务的主机号 |
port | Cloudant 服务的端口号 |
usename | 认证的用户名 |
password | 认证的用户密码 |
url | Cloudant 数据库访问的 url 地址 |
到目前为止,你已经在 Bluemix 上创建了一个基于 liberty profile 的项目,并创建了一个 Cloudant NoSQL 的服务。接下来的问题是如何去管理这个 Cloudant 数据库。Cloudant 提供了一个 web 的 Dashboard,你可以在这个 Dashboard 上面管理你创建的数据库。选择页面左边导航条目中的 SERVICES 下的 Cloudant NoSQL DB 点击右上角的 Launch 按钮。
在 Database 页面的左上角点击 Add New Database,输入 crud 数据库名称,点击 Create 按钮。
图 16.创建 crud 数据库实例
选择 crud 数据库,点击页面左边 All Documents 的加号,创建一个新的文档,如下图 17 所示。
图 17.创建一个文档
回页首
清单 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 数据库提供的 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 数据库 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 支持高级查询的可能。