使用 sqlRest 将数据库转换为 REST 风格的 Web 服务
随着 Web 2.0 的 发展,REST(Representational State Transfer)风格的 Web Service 得到普遍的应用,各种 REST 框架如雨后春笋般发展起来。当我们从事 Web 2.0 的实践时,越来越感觉到服务器端的 REST 服务在应对 MIS 一样的信息系统以及和数据库的交互时还存在很大的问题,比如效率低下,重复编码等。
例如,我们假定如下的场景:需要使用 Web 2.0 的技术来实现一个信息管理系统,这个系统的主要的任务就是对后台数据库的业务数据的操作,管理和报表。这样的场景在如财务等这样的业务系统中非常常见。我们可以使用实体 Bean 实现 EJB,再将该 EJB 包装成为 REST 服务,或者使用轻量级的 Hibernate,配合各种 Java 的 REST 框架来实现,但是无论如何都不可避免需要做很多的重复编码,或者要实现大量的数据库操作。既然 REST 将服务看成是资源的服务,那么我们可以认为数据库中的数据也是一种资源。有没有方法直接将它们转化为 REST 服务呢?sqlRest 正是这样一种框架,它是一种高效的轻量级数据库 REST 服务解决方案,可以通过简单配置直接将数据库中的数据暴露成 REST 风格的 Web 服务,并将数据库的 CRUD 操作和 REST 服务的 GET, DELETE, POST, PUT 接口对应起来。
本文将介绍这一框架的配置和运行,并讨论如何测试 REST 服务,最后给出了配置 DB2 实现 DB2 中的资源 REST 化的参考办法,相信会对 Web 2.0 和 REST 服务的开发者带来一定的参考价值。本文的示例基于 Eclipse 3.2 和 JDK1.5,Servlet 容器使用的是 Tomcat 5.5 。
sqlRest 是 sourceforge 站点上的一个开源项目,它使用一个 Servlet 实现了基于数据库的 REST 引擎,可以自动的将数据库的表映射成 REST 资源,用户可以使用 URL 获取表的内容 (XML 格式),并进行相关操作。
我们首先下载一个二进制的包来运行 sqlRest 示例。您可以通过 http://sourceforge.net/projects/sqlrest/ 访问该项目。在首页上点击下载,进入下载页面下载 sqlrest-0.3.2 版本,这也是目前唯一可用的版本。
下载后解压缩,请先确认安装了 JRE 1.4.1 和 TOMCAT 4.1 以上版本。将解压缩目录内 webapps 目录下的 sqlrest 目录拷贝到 TOMCAT 的 webapps 目录中。启动 TOMCAT,在您的浏览器内键入 http://localhost:8080/sqlrest, (如果您的 TOMCAT 的端口是 8080),您将看见如图 1 的 sqlRest 的 Web 界面:
该页面描述了数据库中有 4 种可用的 REST 资源,这说明安装成功,我们也就可以开始进一步的工作。
这里读者需要注意,根据 JRE 版本和类型的不同,可能在启动的时候 TOMCAT 会出错(参考如图 2 的错误信息),sqlRest 的 Servlet 无法加载,造成错误的原因是我们下载的 sqlRest 是二进制版本,而编译 sqlRest 的 JRE 和我们运行 Tomcat 的 JRE 之间存在不兼容。解决方法是获取 sqlRest 的源码,在运行环境中重新编译,再将编译的 class 文件拷贝到 Tomcat 中的 webapp/sqlrest/WEB-INF 目录中的 classes 目录下重新启动就可以了。如果选择从源码项目开始编译和部署运行,就可以避免这一错误(TOMCAT 和 Eclipse 使用的是同一 JRE),下面您将了解到如何从 CVS 获取代码并从源码开始编译部署 sqlRest 。
sqlRest 在 SourceForge 上的源代码可以通过匿名的 CVS 获取。请先按图 3 的配置方法在 Eclipse 中建立源码仓库。
检出源码项目后,我们发现这是一个 Java 项目,可以使用 ANT 的脚本构建出 Web 部署包。但是为了使用和测试方便,我们需要使用 Eclipse 的动态 Web 项目来调试 sqlRest 。此时需要根据已下载的 sqlRest 的二进制包从该 Java 项目的源码创建一个 Eclipse 的 Web 项目(请确认安装了 WTP 插件,笔者使用的是 WTP1.5 版本),本文以下的示例都是基于该 Web 项目的,笔者使用的开发环境是 Eclipse 3.2 和 JDK 1.5,Servlet 容器使用的是 TOMCAT 5.5 。我们创建的 Web 项目名为 sqlrest,读者可以参考图 4 中的项目结构:
图 4. sqlRest 在 Eclipse 中的 Web Project
读者也可以从附件中下载该 project 并导入到 Eclipse 的工作区。当在 Eclipse 中配置好 TOMCAT 服务器后,我们就可以进行进一步的开发和部署。请注意该 Web 项目的 properties 对话框,您需要将 java 编译的结果输出到 WEB-INF 目录下的 classes 目录,如图 5 所示:
编译后部署该项目,访问 http://localhost:8080/sqlrest,您依然可以看见和图 1 相同的 Web 界面
现在回顾一下图 1 的内容, sqlRest 的示例中包含了 Customer,Product,Invoice 和 Item 4 种 Rest 资源,这四种资源对应着数据库种的 4 张表,sqlRest 会自动的将数据库的记录作为资源通过 RestURL 和服务调用展示出来。现在您可以在浏览器中尝试如下的 URL 来测试这些 Rest 服务。
在浏览器中键入 REST URL | 含义 |
---|---|
http://localhost:8080/sqlrest | 获取配置数据库的所有资源列表 |
http://localhost:8080/sqlrest/INVOICE/ | 获取所有的 invoice 资源列表 |
http://localhost:8080/sqlrest/INVOICE/31/ | 获取 id 号为 31 的 invoice 资源 |
http://localhost:8080/sqlrest/CUSTOMER/22/ | 获取 id 号为 22 的 customer 资源 |
我们知道在浏览器中键入 URL 并回车,浏览器会发出 GET 方法的 HTTP 请求,如果使用的是 REST 风格的 URL(且满足服务器端的约定,因为 REST 是一种风格,而不是标准),并且服务器端提供了对应的 REST 服务,此时浏览器相当于在执行 REST 服务的调用,浏览器页面中的内容就是 REST 服务的返回,也就是该 REST URL 对应的 REST 资源的内容。现在您一定很想知道,这些数据是从哪里来的?显然应该来自数据库,它们应该就是 sqlRest 自带的测试数据库中的数据。下面我们将介绍 sqlRest 数据库的配置,通过简单的配置,sqlRest 可以支持直接将数据库的数据资源暴露成 Rest Web 服务接口。
在我们的 Web 项目中,可以发现 WEB-INF 目录下的 sqlrestconf.xml 文件,该配置文件描述了数据库的信息和登录信息,我们如果打开 WEB-INF/lib 目录,我们将发现数据库驱动程序:hsqldb-1-7-1.jar,根据配置文件我们就得知 sqlRest 默认使用的是 hypersonic 数据库,一种嵌入式的 java 数据库。那么测试使用的数据源到底在哪里呢?
研究源码我们发现,MainServlet 类就是 sqlRest 服务的入口,该 Servlet 用于接受指定 URL 的 HTTP 请求,从该 Servlet 的 getDatabaserUrl 方法中我们可以看见数据源的来源,注意代码中的 data 目录,这个就是示例数据的数据来源,我们打开该目录下的 exampledb.script 文件就发现该数据库包含上文提及的 4 张表。因此我们知道 exampledb 有四张表也就是有四种 REST 资源,sqlRest 会通过映射将这四种资源暴露为 REST 风格的 Web 服务。我们也会发现,数据库中每个表必须有一个 primary key 的定义,这就是资源的唯一标识符。这里我们就了解到 sqlRest 的数据库配置,通过这样简单的配置和 sqlrest 的 Servlet,hpersonic 数据库中的数据就被映射成为 Web 上的具有唯一 URL 的 Rest 资源。请特别注意 Item 表的定义,它定义了 2 个字段的组合作为 primary key,我们可以通过 http://localhost:8080/sqlrest/ITEM/1 来查看 INVOICEID = 1 的 Invoice 中的所有 Item:
图 6. INVOICEID 为 1 的 Invoice 中的所有 Item 资源
由于 sqlRest 目前版本的尚不是很完善,虽然数据库的定义中主键可以包含多个字段,但是在解析参数的时候,只会将参数关联到主键中第一个字段的值,例如即使我们使用 http://localhost:8080/sqlrest/ITEM/1/0 希望能够返回 id 为 1 的 incovice 中 id 为 0 的 Item,但是结果仍然和 图 6 相同。因为 sqlRest 忽略了后面的参数。如果需要,您可以通过修改 sqlRest 源码,使之支持这样的 URL 来通过多参数更为精确的定位数据库中的资源。
在我们的 Web 2.0 实践中,我们使用了形如 http://localhost:8080/sqlrest/INVOICE1/ITEM/0 这样的 URL 来能够更好的表达我们的意思,但是 REST 并没有强制要求 URL 的定义和参数的定义形式,REST 不是规范,也不是标准,只是一种设计风格。在我们的实践中我们觉得这样的 URL 能够更好的表达资源的含义,也有利于服务器端的解析。
到目前为止,我们只对 sqlRest 的 Rest 服务进行了基于浏览器的 GET 资源的测试,我们知道一个 Rest 服务将会实现 GET,DELETE,UPDATE,CREATE 四种类型的接口,我们如何来测试这些接口呢?我们又如何确定 sqlRest 能自动的将这些接口和对数据库的操作 (CRUD) 映射起来了呢 ? 视具体项目的情况,我们有多种的选择。
最简单的情况下,我们只需要使用 JDK 提供的 HTTP 相关的 API 来编写 Java 代码作为 Rest 客户端,此时您无需额外的工具或者 jar 包,这主要会使用到 URL 和 HttpURLConnection 类。读者可以参考清单 2 中的示例代码。
URL url= new URL( “ http://localhost:8080/sqlrest ” ); HttpURLConnection con = (HttpURLConnection)url.openConnection(); con.setRequestMethod("GET"); con.connect(); InputStream in= con.getInputStream(); byte[] b= new byte[1024]; int result= in.read(b); while( result!=-1){ System.out.write(b,0,result); result= in.read(b); }
sqlRest 的源码中提供了一个 RestClient 类 ( 位于 rest.client 包 ),该类是一个封装了很好的 REST 服务 Java 客户端,我们只要稍作修改就能将该 REST 服务的输出返回到字符串,而不是仅仅打印在控制台。
如果不愿意编写测试代码,我们也可以使用一些工具来帮助测试 REST 服务。 Poster 就是这样一个工具,它是一个 Firefox 插件,我们可以从以下的 URL 获取 Poster 的安装下载:https://addons.mozilla.org/zh-CN/firefox/addon/2691 。安装后重新启动 Firefox,从“ View - >SideBar - >Poster ”菜单启动该插件,我们就可以看见如图 7 的界面,该工具简单易用,我们可以轻松的发送出 REST 测试报文并观察其输出。
通过使用 FireFox Poster 插件,您可以发出各种各样的 HTTP 请求,从而达到不编写代码测试 REST 服务的目的。
实际项目中,我们总是希望有一些自动化的方法, sqlRest 的源码中已经提供了基于 JUnit 和 Apache Common HTTP 的自动化测试代码,读者可以参考源码包中的 MainServletTest 类。这是一个 JUnit 测试用例,覆盖了对 sqlRest 服务的各种测试,通过运行该测试用例,我们就实现了对 sqlRest 服务的全面测试。该测试用例使用了 Apache Common HTTP 包和 JUnit 包一起,来自动化的测试 REST 服务。在读者使用其它 RESTRest 框架时,这种测试方法也是有效的。我们也可以在项目中使用 ANT 的脚本来自动的运行 JUnit 的测试,这样就获得了更为全面的自动化的测试能力,使用 ANT 这样的脚本工具,您可以获取从编译到部署再到测试的全方位的自动化能力。读者可以参考 MainServletTest 中的代码构建基于 JUnit 和 Apach Common HTTP 的自动化测试。
显然 REST Service 并不是全部要面向 Java 客户端的,实际的项目中,很多的 Web 2.0 的解决方案后台会使用 REST 服务来提供资源,我们完全可以定义一个简单的 AJAX 客户端(任何客户端本来就具有对服务器端测试的潜在能力)来进行 Rest 服务的测试,虽然最终的效果看起来也许和 Poster 差不多,但是我们的 AJAX 测试是具有业务逻辑的测试,也是一种高级的测试形态。一个典型的例子是我们会在页面中使用 AJAX 方法获取远程的 REST 资源,然后展现给用户。具体可以参考 参考资料 。
笔者使用的是 DB2 8.2 版本。我们将使用 DB2 数据库的 sample 数据库,如果您 DB2 没有配置,您可以通过在 DB2 的命令行中运行 DB2sampl 命令来安装示例数据库, 为简化配置,我们使用本机数据库(DB2 和 Tomcat 在一台机器上), 我们的示例将演示如何将 Sample 数据库中的 EMPLOYEE 表中的数据配置为 REST 服务的资源。
找到 sqlrestconf.xml 将内容修改为如清单 3:
<database> <jdbc-driver-class>com.ibm.db2.jcc.DB2Driver</jdbc-driver-class> <database-url>jdbc:db2:sample</database-url> <user>user</user> <password>pwd123</password> </database> |
这里的用户名和密码请使用读者自己的 DB2 用户名和密码配置。请到 DB 的安装目录下找到 jdbc 驱动程序,例如笔者的目录: D:/Program Files/IBM/SQLLIB/java,拷贝 db2jcc.jar 和 db2jcc_license_cu.jar 到 sqlRest 项目的 WEB - INF 的 lib 目录下,并重新部署。
当配置文件和 DB2 数据库客户端准备完成后,重新部署后我们可以在 Tomcat 控制台中看见 “ Wrong table name ”的错误,启动失败,是什么原因呢?这就因为目前 sqlRest 还在开发过程中,有些功能的发展还不太完善,我们已经在前文提到过,sqlRest 必须要一个主键来表示表中的数据资源,而我们的 Sample 数据库中很多的表没有主键或者主键不满足条件。只有数据库中的表满足 sqlRest 的要求,引擎才能通过 URL 找到唯一的资源,因此为了示例的方便,我们需要对 Sample 数据库做一些调整:
- 删除您的 Schema 下无用的表:仅保留 EMPLOYEE 表;
- 给 EMPLOYEE 表增加主键(EMPNO);
修改后的表如图 8 所示:
此时部署运行,一切正常,在浏览器中 http://localhost:8080/sqlrest 我们将看见系统 EMPLOYEE 表作为 REST 资源暴露出来。 通过这个简单的例子我们认识到 salRest 可以很容易的配置为支持各种数据库,读者也可以尝试用上文类似的 URL 来测试该 EMPLOYEE REST 服务,您可以充分的感受到 sqlRest 的方便和快捷。也许您会问,难道唯一的 URL 只能要求数据库的唯一主键吗?当然不是这样的,这取决于服务器端框架对 URL 的约定,我们完全可以使用更复杂的 URL 来表示资源,但是需要服务器端支持对更复杂 URL 的解析功能和对多字段主键的支持。我们相信 sqlRest 将来的版本将提供对数据库多键的支持,但是现在我们只能使用单一字段作为 URL 中的主键。读者如果需要,可以对 sqlRest 的源码进行一定的修改来支持更为复杂的 URL 格式,这样的修改不是很麻烦。
也许有读者会问,修改现有的表,使其满足 sqlRest 的要求,还算可以容忍,但是删除数据库中其它不满足条件的表就不对了,如果有的是系统表怎么办?在我们的例子中,我们使用 DB2 的示例表主要是从简单的角度出发,实际项目中,可能有两种情况:
1 完全重新设计数据库 : 此时只要满足 sqlRest 主键的要求就没有问题,
2 基于现有数据库的应用 : 此时就需要对现有数据库进行改造,但这显然会影响已有的应用,因此我们不推荐(上文我们对 Sample 数据库的修改只是作为示例)。
对于暂时没有成为 REST 资源的表,又不想改造数据,我们可以通过一个简单的方法来实现。我们的做法是直接修改 sqlRest 的源码,这个修改非常简单,我们只要修改 DatabaseAnalyser 类的 getDatabaseInfo 方法,使得 sqlRest 在解析数据库表的时候不要抛出异常,遇见不满足条件的表就自动跳过就可以,这样的修改不影响已有的满足条件的表。
目前 sqlRest 还有一个限制就是每次配置只能使用一个数据库,这时候因为 URL 的契约中并没有使用数据库的名字作为名字空间,同样的道理,通过对源码的学习我们也意识到修改源码实现对多数据库的支持并不是很困难的事情。使用开源软件的好处之一就在于您可以根据自己的需要对它进行修改。
通过本文我们可以了解到 sqlRest 的设计思想,就是将数据库资源直接暴露为 REST 服务,这样可以大量的节省我们的重复编码,提高 We2.0 时代处理数据库资源的快捷,同时我们也看 sqlRest 的一些不足和有待发展的地方。我们注意到在互联网世界中,存在一些大量使用数据库资源的应用,使用 sqlRest 这种将数据库资源直接暴露为 REST 服务的方式可以极大的提高效率,降低开发和后台维护的成本。对于这样一类应用,sqlRest 这种模式无疑是有很大的用武之地的。因此我们可以预见将来的数据库很可能会内置对 REST 服务的支持,就像对现在的一些数据库会内置对 SOAP Web 服务的支持一样。
声明:本文仅代表作者的个人观点,不代表 IBM 的立场。