基于 HTML5 中的 Web SQL Database 来构建应用程序

HTML5 Web SQL Database 简介

通过 Mark Pilgrim 的 Dive Into HTML5,我们了解到 HTML5 的很多新特性。但 HTML5 标准并不只局限于传统的标记语言,它还拥有很多让人期待的 API 接口,利用这些 API 接口,开发者可以创建更加丰富,更加引人入目的应用程序。比如支持文件拖放上传功能的 HTML5 File API。本文专注于 HTML5 的新特性:Web SQL Database API,使用本地和会话存储实现简单的对象持久化。

对于 Web 应用的存储,相信大家都接触过 Cookie。Cookie 用于弥补 HTTP 协议的无状态性,服务器可以使用 Cookie 中包含的信息来判断 HTTP 传输中的状态。但 Cookie 有自己固有的缺点:它的大小受限,大多数浏览器对 Cookie 大小限制为 4K;Cookie 机制可以在浏览器中被禁用;Cookie 需要在客户端和服务器端来回地传送,繁琐且消耗带宽;存在安全风险,Cookie 是以明文存放,可能被恶意客户修改,当然可以手动加密和解密 Cookie,但这需要额外的编码,并且因为加密和解密需要消耗一定的时间而影响应用程序的性能。

对于 HTML5,也许很有用的就是它新推出的“Web Storage”(Web 存储)API,它包括 localStorage 和 sessionStorage,对简单的键值对(比如应用程序设置)或简单对象(如应用程序状态)进行存储,使用本地和会话存储能够很好地完成,对于存储少量的数据非常有用,但是对大量的结构化数据进行处理时,它就力所不及了,而这正是 HTML5 的“Web SQL Database” API 接口的应用所在。

Web SQL Database API 实际上并不包含在 HTML5 规范之中。它是一个独立的规范,它引入了一套使用 SQL 操作客户端数据库的 API。最新版本的 Chrome,Safari 和 Opera 浏览器都支持 Web SQL Database


Web SQL Database

在 W3C 的 Web SQL Database 规范中(参照 介绍)有这样的描述:Web SQL Database 引入了一套使用 SQL 来操纵客户端数据库(client-side database)的 API,这些 API 是异步的(asynchronous),所以作者在使用这套 API 时会发现匿名函数非常有用。规范中所使用的 SQL 语言为 SQLite 3.6.19。

其中 SQLite 是一款轻型的数据库,是遵循 ACID 的关系型数据库管理系统。它的设计目标是嵌入式的,它占用资源非常低,只需要几百 K 字节的内存就可以了。它能够支持 Windows/Linux/Unix 等主流操作系统,同时能够跟很多程序语言相结合,如 C#,PHP,Java,JavaScript 等,还有 ODBC 接口,比起 Mysql,PostgreSQL 这两款开源的数据库管理系统来说,它的处理速度更快。

本文将介绍 Web SQL Database 规范中定义的三个核心方法:

  1. openDatabase:这个方法使用现有数据库或新建数据库来创建数据库对象
  2. transaction:这个方法允许我们根据情况控制事务提交或回滚
  3. executeSql:这个方法用于执行真实的 SQL 查询

注意:对于下面的内容需要读者对 JavaScript 和面向对象编程(特别是匿名内部类的内部函数)以及 SQL 具有很好的理解。


HTML5 Web SQL Database API

1.Database

每个域都有一组相关的数据库,每个数据库有名字和当前的版本号。这套 API 并不提供遍历或删除域中的某个数据库的功能。每个数据库一时间只能有一个版本号,不能一时间拥有多个版本号,版本号用来保护数据库不被写入脏数据。

清单 1.Database API
 [Supplemental, NoInterfaceObject]  interface WindowDatabase {  Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName,  in unsigned long estimatedSize, in optional DatabaseCallback creationCallback); };  Window implements WindowDatabase;  [Supplemental, NoInterfaceObject]  interface WorkerUtilsDatabase {  Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName,  in unsigned long estimatedSize, in optional DatabaseCallback creationCallback); DatabaseSync openDatabaseSync(in DOMString name, in DOMString version,  in DOMString displayName, in unsigned long estimatedSize,  in optional DatabaseCallback creationCallback); };  WorkerUtils implements WorkerUtilsDatabase;  [Callback=FunctionOnly, NoInterfaceObject]  interface DatabaseCallback {   void handleEvent(in Database database); };

接口 Window、WorkerUtils 的 openDatabase() 方法和接口 WorkerUtils 的 openDatabaseSync() 方法接受以下参数:一个数据库名字(name),一个数据库版本号(version),一个显示名字(displayName),数据库将要保存数据的大小(estimatedSize,以字节为单位 ),一个可选的回调函数(createionCallback,如果数据库没有被创建,这个函数将会被调用 )。如果提供了回调函数,回调函数用以调用 changeVersion() 函数,不管给定什么样的版本号,回调函数将把数据库的版本号设置为空;如果没有提供回调函数,则以给定的版本号创建数据库。

包括空字符串在内的所有字符串都可以作为有效地数据库名称,数据库名称区分大小写,且可以比较。

2.异步数据库 API(Asynchronous Database API)

清单 2. 异步数据库 API
 interface Database {   void transaction(in SQLTransactionCallback callback,   in optional SQLTransactionErrorCallback errorCallback,   in optional SQLVoidCallback successCallback);  void readTransaction(in SQLTransactionCallback callback,   in optional SQLTransactionErrorCallback errorCallback,   in optional SQLVoidCallback successCallback);  readonly attribute DOMString version;  void changeVersion(in DOMString oldVersion,   in DOMString newVersion, in optional SQLTransactionCallback callback,   in optional SQLTransactionErrorCallback errorCallback,    in optional SQLVoidCallback successCallback); };  [Callback=FunctionOnly, NoInterfaceObject]  interface SQLVoidCallback {   void handleEvent(); };  [Callback=FunctionOnly, NoInterfaceObject]  interface SQLTransactionCallback {   void handleEvent(in SQLTransaction transaction); };  [Callback=FunctionOnly, NoInterfaceObject]  interface SQLTransactionErrorCallback {   void handleEvent(in SQLError error); };

方法 transaction() 和 readTransaction() 有一个到三个参数,后两个参数为可选的,分别表示错误回调函数和成功回调函数。transaction() 方法为 read/write 模式,readTransaction() 方法为只读模式;获取属性 version 必须返回数据库的当前版本号;方法 changeVersion 允许脚本自动地检查版本号,并修改它。

2.1 执行 SQL 语句

清单 3. 执行 SQL 语句 API
 typedef sequence<any> ObjectArray;  interface SQLTransaction {   void executeSql(in DOMString sqlStatement, in optional ObjectArray arguments,   in optional SQLStatementCallback callback,    in optional SQLStatementErrorCallback errorCallback); };  [Callback=FunctionOnly, NoInterfaceObject]  interface SQLStatementCallback {   void handleEvent(in SQLTransaction transaction, in SQLResultSet resultSet); };  [Callback=FunctionOnly, NoInterfaceObject]  interface SQLStatementErrorCallback {   boolean handleEvent(in SQLTransaction transaction, in SQLError error); };

这个函数具有四个参数:表示查询的字符串(sqlStatement);插入到查询语句中问号所在处的字符串数据(arguments);一个可选的成功时执行函数(callback);一个可选的失败时执行函数(errorCallback)。

3.数据库查询结果(Database Query Result)

清单 4. 查询结果集 API
 interface SQLResultSet { readonly attribute long insertId;readonly attribute long rowsAffected;readonly attribute SQLResultSetRowList rows; };

如果插入数据库一行数据,insertId 代表这个行号;如果插入多行数据,insertId 代表插入数据的最后一行行号。

SQL 语句执行后改变的行数用 rowsAffected 表示,如果 SQL 语句没有改变任何行,则 rowsAffected 为 0,对于“SELECT”语句,rowsAffected 就为 0.

rows 为一个 SQLResultSetRowList 对象,代表数据库按顺序返回的行。如果没有返回任何行,则这个对象为空。


一个入门的例子详解

本例将完整地演示 Web SQL Database API 的使用,建立数据库、建立表格、插入数据、查询数据、将查询结果显示。这个例子只能在最新版本的 Chrome、Safari 或 Opera 浏览器中产生输出结果。

清单 5. 例子源码
1. <!DOCTYPE HTML>2. <html>3. <head>4. <script type="text/javascript">5. var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);6. var msg;7. db.transaction(function (tx) {8.    tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');9.    tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "foobar")');10.    tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "logmsg")');11.    msg = '<p>Log message created and row inserted.</p>';12.    document.querySelector('#status').innerHTML =   msg;13. });14.15. db.transaction(function (tx) {16.    tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) {17.    var len = results.rows.length, i;18.     msg = "<p>Found rows: " + len + "</p>";19.     document.querySelector('#status').innerHTML +=   msg;20.    for (i = 0; i < len; i++){21.       msg = "<p><b>" + results.rows.item(i).log + "</b></p>";22.       document.querySelector('#status').innerHTML +=   msg;23.     }24. }, null);25. });26. </script>27. </head>28. <body>29. <div id="status" name="status">Status Message</div>30. </body>31. </html>

第五行的 var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);

建立一个名称为 mydb 的数据库,它的版本为 1.0,描述信息为 Test DB,大小为 2M 字节。openDatabase 方法打开一个已经存在的数据库,如果数据库不存在则创建数据库,创建数据库的语法如下:

Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize, in optional DatabaseCallback creationCallback)

方法用到五个参数:

  1. 数据库名
  2. 版本号
  3. 描述
  4. 数据库大小
  5. 创建回调函数

最后一个参数创建回调函数,在创建数据库的时候调用,但即使没有这个参数,一样可以运行时创建数据库。

执行创建数据库后在 Chrome 中可以看到如下情形:

图 1. 创建数据库 mydb
图 1. 创建数据库 mydb

可以看到此时有数据库建立,但并无表格建立。

第七行到第十三行:

7. db.transaction(function (tx) {8.    tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');9.    tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "foobar")');10.    tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "logmsg")');11.    msg = '<p>Log message created and row inserted.</p>';12.    document.querySelector('#status').innerHTML =   msg;13. });

通过第八行语句可以在 mydb 数据库中建立一个 LOGS 表格。在这里只执行创建表格语句,而不执行后面两个插入操作时,将在 Chrome 中看到如下的情形:

图 2. 在数据库 mydb 中建立表格 LOGS
图 2. 在数据库 mydb 中建立表格 LOGS

可以看到在数据库 mydb 中有表格 LOGS 建立,但表格 LOGS 为空。

九,十两行执行插入操作,在插入新记录时,我们还可以传递动态值,如:

 var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); db.transaction(function (tx) {     tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');    tx.executeSql('INSERT INTO LOGS (id,log) VALUES (?, ?'), [e_id, e_log]; });

这里的 e_id 和 e_log 为外部变量,executeSql 在数组参数中将每个变量映射到“?”。

在插入操作执行后,可以在 Chrome 中看到数据库的状态:

图 3. 在数据库 mydb 的表格 LOGS 中插入数据
图 3. 在数据库 mydb 的表格 LOGS 中插入数据

可以看到插入的数据,此时并未执行查询语句,可以看到页面中的显示如下,并没有出现查询结果:

图 4. 插入数据后的页面
图 4. 插入数据后的页面

如果要读取已经存在的记录,我们使用一个回调函数捕获结果,如上面的第十五到第二十五行代码:

15. db.transaction(function (tx) {16.    tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) {17.    var len = results.rows.length, i;18.     msg = "<p>Found rows: " + len + "</p>";19.     document.querySelector('#status').innerHTML +=   msg;20.    for (i = 0; i < len; i++){21.       msg = "<p><b>" + results.rows.item(i).log + "</b></p>";22.       document.querySelector('#status').innerHTML +=   msg;23.     }24. }, null);25. });

执行查询之后,将信息输出到页面中,可以看到页面中状态如下:

图 5. 执行查询后的页面
图 5. 执行查询后的页面

结束语

本文介绍了 HTML5 的 Web SQL Database 特点,对其 API 进行介绍。需要注意的是,如果不是绝对需要,不要使用 Web SQL Database,因为它会让我们的代码更加复杂(匿名内部类的内部函数,回调函数等等)。对大多数情况下,本地存储或会话存储就能够完成相应的任务,尤其是你能够保持对象状态持久化的情况。通过这些 HTML5 Web SQL Database API 接口,可以获得更多功能,相信以后会出现一些非常优秀的、建立在这些 API 之上的应用程序。

参考资料

学习

有话要说