HTML5项目笔记5:使用HTML5 WebDataBase设计离线数据库
基于HTML5的Web DataBase 可以让你在浏览器中进行数据持久地存储管理和有效查询,假设你的离线应用程序有需要规范化的存储功能,那么使用Web DataBase,可以使你的应用程序无论是在离线或者在线或者网络不通畅情况下都可以将数据保存在客户端。
下面是HTML5 DataBase中两个不同的DataBase的比较,摘自http://www.html5rocks.com/en 上面的一篇文章。
我们这边使用WebSQL来设计和编写底层服务,W3C 的 WebDatabase 规范中说这份规范不再维护了,但是几乎实现者都选择了SQLite这种轻量简单易用的客户端数据库:
现在我们来封装和提取WebSQL公用方法。
首先,我们需要拿到SQLite数据库可操作和执行的SQL数据上下文:
这边通过openDatatBase方法打开或创建数据库:
1 /*-------执行SQLite注入,数据库的基本操作(Begin)-------*/ 2 function SQLProvider(dbName, size) { 3 this.dbName = dbName || 'OFLMAIL'; 4 5 var db = openDatabase(this.dbName, '1.0', 'database for ' + this.dbName, (size || 2) * 1024 * 1024); 6 this.db = db; 7 8 /*-------执行SQLite注入,数据库的基本操作(End)-------*/ 9 10 function sqlerrorHandler(tx, e) { 11 log.error(e.message); 12 }
这边还可以设置数据库的名称dbName和数据库大小size,默认数据库名称是OFLMAIL,就是我们这个离线系统的名称,默认大小是2兆。
我们还可以设置错误处理方法sqlErrorHandler,用户处理操作失败之后的错误捕捉
这样,我们就拿到了操作SQLite的数据上下文db,通过上下文db,我们可以执行相应的CURD操作。
第一步,我们写一个创建数据表的方法,把这个方法放在SQLProvider方法体里面,
1 /*--添加数据表--*/ 2 this.createTable = function (tableName, fields, callBack) { 3 var pkField = tableName + "_SEC"; 4 var sql = "CREATE TABLE IF NOT EXISTS " + tableName + "( " + pkField + " integer primary key autoincrement,"; 5 6 // 合并字段串同时去除传入的主键字段 7 sql += fields.join(",").replace(pkField + ",", "") + ")"; 8 //log.debug(sql); 9 10 db.transaction(function (tx) { 11 tx.executeSql(sql, [], function () { 12 if (callBack) callBack(); 13 }, sqlerrorHandler); 14 }) 15 }
一共包含了三个参数tableName,fileds,callBack,分表代表你要创建的表名,所对应的字段数组,就是把这个表相应的字段用数组保存起来(方法里面还会自动创建一个表名加上“_SEC”的字段,他是个增量标识,用做主键),callBack顾名思义,回调函数,这个参数可以不传。这个回调函数的存在很重要,因为整个基于SQLite数据库的操作方法都是异步调用的,所以需要在回调函数中嵌套执行,否则有些执行会被中断。
将这个函数放在SQLProvider里面,有一个好处就是到时候可以在SQLProvider的动态实例化中直接调用该函数
如:var sqlProvider = new SQLProvider();
sqlProvider.createTable(“UserInfo”,new Array(“UserName”,”UserPwd”));
这样子,方便我们在页面中调用。这种操作方法相当于C#里面的动态类创建方法,SQLProvider就是类名,createTable就是类中的方法,实例化调用。
接下来我们的数据库的操作,包括数据表和数据的CURD操作,都会以这种方法写在里面:
删除数据表(只需传入表名就行了,他会删除相应的数据表):
1 /*--删除数据表--*/ 2 this.dropTable = function (tableName) { 3 var sql = "DROP TABLE " + tableName; 4 db.transaction(function (tx) { 5 tx.executeSql(sql); 6 }) 7 }
添加数据(包含了四个参数:表名,字段数组,字段所对应的值的数组,和一个回调函数)
这边的fields和values代表了字段数组和值数组,他们一一对应:
如 var fileds=new Array(“UserName”,”UserPwd”);
var values=new Array(“Ben”,”123456”);则说明在UserInfo表里面添加了一条数据,这条数据至少包含三个有值的字段,主键,UserName和UserPwd,而values 则是相应的值数组。
回调函数中带有一个返回的参数,返回了你所添加的改行数据的主键。
1 /*--添加数据(插入数据)--*/ 2 this.insertRow = function (tableName, fields, values, callback) { 3 var sql = "INSERT INTO " + tableName + " (" + fields.join(",") + ") SELECT " 4 + new Array(values.length + 1).join(",?").substr(1); 5 6 db.transaction(function (tx) { 7 tx.executeSql(sql, values, function () { }, sqlerrorHandler); 8 //log.debug(sql); 9 10 tx.executeSql("SELECT max(" + tableName + "_SEC) id from " + tableName, [], function (tx, result) { 11 var item = result.rows.item(0); 12 var id = parseInt(item.id); 13 //log.debug("id=" + id); 14 if (callback) callback(id); 15 }, sqlerrorHandler); 16 }); 17 }
删除数据 (包含了三个参数:表名tableName,主键sec和一个回调函数callback)
这个主键SEC是该待删除的数据在Web DataBase中的主键,我们前面在建表的时候有一个增量标识字段,该字段的名称为表名加上“_SEC”,因为唯一性,所以我们可以根据这个主键来删除该行数据,
代码如下:
1 /*--删除数据--*/ 2 this.deleteRow = function (tableName, sec,callback) { 3 var pkField = tableName + "_SEC"; 4 var sql = "DELETE FROM " + tableName + " WHERE " + pkField + " = ?"; 5 db.transaction(function (tx) { 6 tx.executeSql(sql, [sec], null, sqlerrorHandler); 7 if (callback) callback(); //使用回调 8 }) 9 }
修改数据(这边包括了四个参数,表名tableName,字段数组fields,值数组values,回调函数callback)
字段数组和值数组必须是一一对应的,而且第一个字段必须是主键,所对应的values的第一个值也必须是主键的值,这样,可以根据字段的主键来查询相应的数据行。
查出的数据行之后,可以根据后面的相应字段,进行修改。
1 /*--更新列,这边需要注意的是两个参数列表的首位必须是主键(或者说第一个必须是条件,后面的是修改位)--*/ 2 this.updateRow = function (tableName, fields, values,callback) { 3 var len = fields.length; 4 5 var sql = ""; 6 for (i = 1; i < len; i++) { 7 if (i == 1) sql += fields[i] + " = '" + values[i] + "'"; 8 else sql += "," + fields[i] + " = '" + values[i] + "'"; 9 } 10 11 sql = 'UPDATE ' + tableName + ' SET ' + sql + ' where ' + fields[0] + '= ?'; 12 //log.debug("sql:" + sql); 13 14 db.transaction(function (tx) { 15 tx.executeSql(sql, [values[0]], 16 null, sqlerrorHandler); 17 //log.debug("update " + tableName + " success! sec=" + values[0]); 18 if (callback) callback(); 19 }); 20 } 21 }
调用方式类似如下:
var fileds=new Array(“UserInfo_SEC”,“UserName”,”UserPwd”);
var values=new Array(“5”,“Ben”,”123456”);
sqlProvider.updateRow(“UserInfo”,fileds ,values,function(){
log.debug(“修改成功!”);
});
这样子就是在UserInfo表里面修改主键为5的数据行,修改它的UserName的值为:“Ben”,
修改它的UserPwd的值为:“123456”
根据主键查询单行数据(包含三个参数表名tableName,主键SEC,回调函数callback):
根据表名和主键名称获取到该行数据,并返回,注意到这边通过cllback回调函数来返回查询的结果,通过数据上下文tx执行该SQL脚本,返回的是结果集result,这边我们取他结果集的第一条数据也即是result.rows.item(0),实际上结果集中也只有一条数据。
1 /*--读取单行数据--*/ 2 this.readRow = function (tableName, sec, callback) { 3 db.transaction(function (tx) { 4 tx.executeSql('SELECT * FROM ' + tableName + ' WHERE ' + tableName + '_SEC = ?', [sec], function (tx, result) { 5 if (callback) callback(result.rows.item(0)); // 使用回调 6 }, sqlerrorHandler); 7 }); 8 }
读取指定的数据表(根据表名来读取相应的数据表,并返回结果集):
1 /*--读取数据表--*/ 2 this.loadTable = function (tableName, callback) { 3 db.transaction(function (tx) { 4 tx.executeSql('SELECT * from ' + tableName, [], function (tx, result) { 5 if (callback) callback(result); //使用回调 6 }, sqlerrorHandler); 7 }); 8 }
结果集result中的列的集合用result.rows表示
列的数量用result.rows.length来表示
单条数据是用result.rows.item(index)表示,index指的是列的索引位置,从0开始
根据SQL的where条件语句来读取指定的数据表(根据表名tableName和sqlSenten条件语句来执行,并返回结果集):
1 /*--根据查询条件读取数据表--*/ 2 this.loadTableBySQl = function (tableName, sqlSenten, callback) { 3 db.transaction(function (tx) { 4 tx.executeSql('SELECT * from ' + tableName+" WHERE "+ sqlSenten, [], function (tx, result) { 5 if (callback) callback(result); //使用回调 6 }, sqlerrorHandler); 7 }); 8 }
与上面的方法类似,只是多了一个sqlSenten条件语句来筛选数据
根据某个字段检查是否存在该列(通过字段名和字段所对应的值)来进行操作,过多地用于根据主键来查询数据行:
1 /*--检查是否已存在该列--*/ 2 this.checkExist = function (tableName, fieldName, fieldValue, callback) { 3 db.transaction(function (tx) { 4 tx.executeSql('SELECT * from ' + tableName + ' where ' + fieldName + '= ?', [fieldValue], function (tx, result) { 5 var isExist; 6 if (result.rows.length == 1) isExist = "1"; else isExist = "0"; //1代表存在该行,0 代表不存在该行 7 if (callback) callback(isExist); 8 }, sqlerrorHandler); 9 }); 10 }
当检索读到的结果集合中包含了一条数据的时候,返回1,代表存在该行,为0的时候代表不存在该行。这边做的其实不完善只能在唯一值的字段中才能够过正确显示,如主键,此外还可以通过where条件语句来验证是否存在该行。这边就不说了,自己去尝试。
这样就完成了整个离线数据库的CURD操作,如果有不够的地方,我们可以继续修改完善,完整代码如下,在代码的结尾我们进行了实例化,我们把这些代码独立地存放到WebDataBase.js文件里面,这样可以在继承这个脚本文件的页面里直接调用这个脚本库的方法。
现在我们把这些数据库的的操作应用到我们的系统中,
我们的用户信息页面(Information.html),用来保存登录用户的个人信息的:
包含了如下字段:姓名,性别,入职时间,工号和部门:
在载入的时候查看是否有数据,有数据则显示第一条
1 $(document).ready(function () { 2 sqlProvider.loadTable("UserInfo", function (result) { 3 // result.rows 获取到所有数据行 4 if (result.rows.length > 0) { 5 var row = result.rows.item(0); 6 $("#UserName").val(row.UserName); 7 $("#UserSex").val(row.UserSex); 8 $("#ReportDutyTime").val(row.ReportDutyTime); 9 $("#JobNumber").val(row.JobNumber); 10 $("#DepartmentNumber").val(row.DepartmentNumber); 11 //这边包含一个隐藏域,可以保存该用户信息的主键 12 $("#UserInfo_SEC").val(row.UserInfo_SEC); 13 } 14 }) 15 })
我们的保存按钮的代码如下:
1 function onformsumit() 2 { 3 //创建用户信息表(存在跳过,不存在创建),包含六个字段, 4 //因为创建的时候会自动创建一个UserInfo_SEC的主键,所以实际上是6个字段 5 //UserName:用户名称 6 //UserSex:用户性别 7 //ReportDutyTime:入职时间 8 //JobNumber:工号 9 //DepartmentNumber:部门 10 //Remark:备注 11 12 13 var UserName = $("#UserName").val(); 14 var UserSex = $("#UserSex").val(); 15 var ReportDutyTime = $("#ReportDutyTime").val(); 16 var JobNumber = $("#JobNumber").val(); 17 var DepartmentNumber = $("#DepartmentNumber").val(); 18 var Remark = ""; 19 20 var fields = new Array("UserName", "UserSex", "ReportDutyTime", "JobNumber", "DepartmentNumber", "Remark"); 21 var values = new Array(UserName,UserSex,ReportDutyTime,JobNumber,DepartmentNumber,Remark); 22 sqlProvider.createTable("UserInfo", fields, function () { 23 log.debug("创建数据表UserInfo"); 24 25 var UserInfo_SEC = $("#UserInfo_SEC").val(); 26 //取隐藏域的值,如果是0则为保存不为0则为修改 27 if (UserInfo_SEC == "0") { 28 sqlProvider.insertRow("UserInfo", fields, values, function () { 29 log.debug("插入数据成功!"); 30 alert("保存成功!"); 31 }); 32 } 33 else { 34 sqlProvider.updateRow("UserInfo", fields, values, function () { 35 log.debug("修改数据成功!"); 36 alert("保存成功!"); 37 }); 38 } 39 }); 40 return false; 41 }
保存成功之后,数据就存储在我们离线的数据库里面了,载入时显示在页面效果如下:
我们去浏览器中的DataBase中查看,就可以看到这条数据了,如图:
//以下是表单重置函数,删掉该用户信息的代码
function resets() {
var UserInfo_SEC = $("#UserInfo_SEC").val();
if (UserInfo_SEC != "0") {
sqlProvider.deleteRow("UserInfo", UserInfo_SEC, function () {
window.location.reload(true);
})
}
}
2010.11.18, W3C 宣布 将不再关注Web SQL databas,并且不再维护它的过时的规范,浏览器厂商也不会再在他们的新版浏览器中更新和升级这一块,取而代之的就是IndexedDB,W3C组织鼓励和推崇使用IndexedDB。所以,建议学习人员去看一下IndexedDB的使用方法。
这是相关材料:http://www.html5rocks.com/en/tutorials/webdatabase/websql-indexeddb/
本文的源码:CRX_Mail_WebDataBase