http://www.ibm.com/developerworks/cn/web/1108_zhaifeng_websqldb/
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 规范中定义的三个核心方法:
- openDatabase:这个方法使用现有数据库或新建数据库来创建数据库对象
- transaction:这个方法允许我们根据情况控制事务提交或回滚
- 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)
方法用到五个参数:
- 数据库名
- 版本号
- 描述
- 数据库大小
- 创建回调函数
最后一个参数创建回调函数,在创建数据库的时候调用,但即使没有这个参数,一样可以运行时创建数据库。
执行创建数据库后在 Chrome 中可以看到如下情形:
图 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
可以看到在数据库 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 中插入数据
可以看到插入的数据,此时并未执行查询语句,可以看到页面中的显示如下,并没有出现查询结果:
图 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. 执行查询后的页面
结束语
本文介绍了 HTML5 的 Web SQL Database 特点,对其 API 进行介绍。需要注意的是,如果不是绝对需要,不要使用 Web SQL Database,因为它会让我们的代码更加复杂(匿名内部类的内部函数,回调函数等等)。对大多数情况下,本地存储或会话存储就能够完成相应的任务,尤其是你能够保持对象状态持久化的情况。通过这些 HTML5 Web SQL Database API 接口,可以获得更多功能,相信以后会出现一些非常优秀的、建立在这些 API 之上的应用程序。
参考资料
学习
- 关于 HTML5 的入门资料,请参考 Dive into HTML5。
- 关于 Web SQL Database 的规范,请参考 Web SQL Database。
- 关于 Web SQL Database 的相关例子,请参考 Web SQL Database Sample。
- 关于 Web SQL Database 与其他技术的比较,请参考 WebApps' Database-related Specifications。
- developerWorks Web development 专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
- developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
- developerWorks Web 2.0 资源中心,这是有关 Web 2.0 相关信息的一站式中心,包括大量 Web 2.0 技术文章、教程、下载和相关技术资源。您还可以通过 Web 2.0 新手入门 栏目,迅速了解 Web 2.0 的相关概念。
- 查看 HTML5 专题,了解更多和 HTML5 相关的知识和动向。
讨论
- 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。