web 数据库
之前的章节有讨论过 web 中的存储方式,包括传统的 cookie 和新的 localstorage,这两种方式实现了 HTML 中的离线存储,但是存储方式比较简单,在有些复杂的业务场景可能不能满足条件。本章我们介绍一个计算机中一个重要的学科数据库,以及它在 HTML5 中的支持。数据库是一个内容庞大的知识体系,本章只介绍一些简单的用法以及它在 HTML 中的适用场景。
1. 适用场景
既然适用 localstorage 也可以做简单的数据存储,那么为什么还需要适用数据库呢?假设一个业务场景中将所有用户信息临时存储到浏览器中,这些信息包括昵称、姓名、性别等,现在需要搜索出性别是男的所有用户。如果使用 localstorage 的话,需要将所有的数据提取出来,一条条遍历,得出结果。这样的搜索算法的时间复杂度是 O(n),性能较差。如果使用数据库存储的话,只需要给性别列加上索引,然后使用 SQL 搜索,时间复杂度是 O(lgn),性能提升了一个等量级。关系型数据库的特点是:
- 数据模型基于关系,结构化存储,完整性约束;
- 支持事务,数据一致性;
- 支持 SQL,可以复杂查询;
缺点是:
- SQL 解析会影响性能;
- 无法适应非结构化存储;
- 横向扩展代价高;
- 入门门槛较高。
2. Web SQL
Web SQL不是 HTML5 标准中的一部分,它是一个独立的规范,引入了 SQL 的 api,关于 SQL 的语法可以参考第三方的教程,在此不做解释。Web SQL 有 3 个函数
2.1 openDatabase
这个函数用于打开一个数据库,如果数据库不存在就创建。它有 5 个参数,分别表示:
- 数据库名称;
- 版本号;
- 数据库备注;
- 初始化数据大小;
- 创建/打开成功回调函数
/**
* 创建数据库 或者此数据库已经存在 那么就是打开数据库
* name: 数据库名称
* version: 版本号
* displayName: 对数据库的描述
* estimatedSize: 设置数据的大小
* creationCallback: 回调函数(可省略)
*/
var db = openDatabase("MySql", "1.0", "数据库描述", 1024 * 1024);
2.2 transaction
这个函数使用事务执行 SQL 语句,它是一个闭包,例如:
dataBase.transaction( function(tx) {
tx.executeSql(
"create table if not exists test (id REAL UNIQUE, name TEXT)",
[],
function(tx,result){ alert('创建test表成功'); },
function(tx, error){ alert('创建test表失败:' + error.message);
});
});
2.3 executeSql
这个方法用于执行 SQL 语句。
tx.executeSql(
"update stu set name = ? where id= ?",
[name, id],
function (tx, result) {
},
function (tx, error) {
alert('更新失败: ' + error.message);
});
});
3. indexedDB
IndexedDB 是 HTML5 规范里新出现的浏览器里内置的数据库。它提供了类似数据库风格的数据存储和使用方式。存储在 IndexedDB 里的数据是永久保存,不像 cookies 那样只是临时的。IndexedDB 里提供了查询数据的功能,在线和离线模式下都能使用。
3.1 对比 Web SQL
跟 WebSQL 不同的是,IndexedDB 更像是一个 NoSQL 数据库,而 WebSQL 更像是关系型数据库。
3.2 判断浏览器是否支持
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
if(!window.indexedDB){
console.log("你的浏览器不支持IndexedDB");
}
3.3 创建库
使用 open 方法创建数据库。
var request = window.indexedDB.open("testDB", 2);//第一个参数是数据库的名称,第二个参数是数据库的版本号。版本号可以在升级数据库时用来调整数据库结构和数据
request.onsuccess = function(event){
console.log("成功打开DB");
}//成功之后的回调函数
3.4 添加数据
var transaction = db.transaction(["students"],"readwrite");//先创建事务,具有读写权限
transaction.oncomplete = function(event) {
console.log("Success");
};
transaction.onerror = function(event) {
console.log("Error");
};
var test = transaction.objectStore("test");
test.add({rollNo: rollNo, name: name});//添加数据
3.5 查询
var request = db.transaction(["test"],"readwrite").objectStore("test").get(rollNo);//创建具备读写功能的事务
request.onsuccess = function(event){
console.log("结果 : "+request.result.name);
};//成功查询的回调函数
3.6 修改
var transaction = db.transaction(["test"],"readwrite");//创建事务
var objectStore = transaction.objectStore("test");
var request = objectStore.get(rollNo);
request.onsuccess = function(event){
console.log("Updating : "+request.result.name + " to " + name);
request.result.name = name;//修改数据
objectStore.put(request.result);//执行修改
};
3.7 删除
//创建事务,并删除数据
db.transaction(["students"],"readwrite").objectStore("students").delete(rollNo);
4. 实际项目应用
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>离线记事本</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script><!-- 引用jQuery插件 -->
</head>
<script>
var datatable = null;
var db = openDatabase("note", "", "notebook", 1024 * 100);
//初始化函数方法
function init() {
datatable = document.getElementById("datatable");
showAllData();
}
function removeAllData() {
for(var i = datatable.childNodes.length - 1; i >= 0; i--) {
datatable.removeChild(datatable.childNodes[i]);
}
var tr = document.createElement("tr");
var th1 = document.createElement("th");
var th2 = document.createElement("th");
var th3 = document.createElement("th");
th1.innerHTML = "标题";
th2.innerHTML = "内容";
th3.innerHTML = "时间";
tr.appendChild(th1);
tr.appendChild(th2);
tr.appendChild(th3);
datatable.appendChild(tr);
}
//显示数据库中的数据
function showData(row) {
var tr = document.createElement("tr");
var td1 = document.createElement("td");
td1.innerHTML = row.title;
var td2 = document.createElement("td");
td2.innerHTML = row.content;
var td3 = document.createElement("td");
var t = new Date();
t.setTime(row.time);
td3.innerHTML = t.toLocaleDateString() + " " + t.toLocaleTimeString();
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
datatable.appendChild(tr);
}
//显示所有的数据
function showAllData() {
db.transaction(function(tx) {
tx.executeSql("CREATE TABLE IF NOT EXISTS item(title TEXT,content TEXT,time INTEGER)", []);
tx.executeSql("SELECT * FROM item", [], function(tx, rs) {
removeAllData();
for(var i = 0; i < rs.rows.length; i++) {
showData(rs.rows.item(i))
}
})
})
}
//添加一条记事本数据
function addData(title, content, time) {
db.transaction(function(tx) {
tx.executeSql("INSERT INTO item VALUES (?,?,?)", [title, content, time], function(tx, rs) {
alert("保存成功!");
},
function(tx, error) {
alert(error.source + "::" + error.message);
}
)
})
}
//点击保存按钮
function saveData() {
var title = document.getElementById("name").value;
var content = document.getElementById("memo").value;
var time = new Date().getTime();
addData(title, content, time);
showAllData();
}
</script>
<body onload="init()">
<div data-role="page" id="pageone">
<div data-role="header" data-position="fixed">
<h1>离线记事本</h1>
</div>
<div data-role="main" class="ui-content">
<p align="center">记事</p>
<table data-role="table" class="ui-responsive">
<thead>
<tr>
<th>标题:</th>
<th>内容:</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" id="name"></td>
<td><input type="text" id="memo"></td>
</tr>
</tbody>
</table>
<button type="submit" onclick="saveData()">保存</button>
<table data-role="table" data-mode="" class="ui-responsive" id="datatable">
</table>
</div>
</div>
</body>
</html>
上述代码通过使用 websql 实现了一个简单的离线记事本的功能,数据库中保留 3 个字段,分别是标题、内容、时间,点击保存按钮调用insert
豫剧将数据添加到数据库,然后通过使用 select
语句将数据库中的数据展示出来。如果浏览器不主动清空数据的情况下离线数据将会永久保存,这样的话借助 websql 可以实现与桌面应用相差无几的功能。
5. indexedDB 和 websql 对比
-
访问限制: indexdb 和 websql 一致,均是在创建数据库的域名下才能访问,且不能指定访问域名。
-
存储时间: 这两位的存储时间也是永久,除非用户清除浏览器数据,可以用作长效的存储。
-
大小限制: 理论上讲,这两种存储的方式是没有大小限制的。然而 indexeddb 的数据库超过50M的时候浏览器会弹出确认,基本上也相当于没有限制了。但是由于不同的浏览器的实现有一定的差别,实际使用中需要根据不同的浏览器做相应的容量判断容错。
-
性能测试: indexeddb 查询少量数据花费差不多 20MS 左右。大量数据的情况下,相对耗时会变长一些,但是也就在 30MS 左右,也是相当给力了,10W 数据+,毕竟 nosql。而 websql 的效率也不错,10w+ 数据,简单查询一下,只花费了20MS左右。
-
标准规范: Web SQL 数据库是一个独立的规范,因为安全性能等问题,官方现在也已经放弃了维护;indexedDB 则属于 W3C 标准。
6. 小结
回顾本章,由关系数据库的优缺点及适用场景引申到 HTML5 中的数据库解决方案,以及使用方法,需要注意的是在使用 HTML 数据库的过程中需要检测浏览器是否支持数据库。实际开发项目由于考虑前端数据库的安全性以及性能等问题,如果切实需要使用需要谨慎,毕竟一般项目中数据库保存的都是敏感数据,即使保存在服务器中也需要一定的安全加密措施,所以一般前端存储的都是一些临时的数据。