indexeddb_在HTML5中利用IndexedDB API

indexeddb

使用HTML5数据库和离线功能,第1部分 ”介绍了HTML5规范中可用的离线应用程序和本地数据持久性选项,重点是localStorage。 本部分介绍Indexed Database(IndexedDB),它是HTML5标准的一部分,是一种健壮的数据持久性技术,并讨论了如何将IndexedDB数据提供程序与您在第一篇文章中创建的Contact Manager应用程序集成。

Contact Manager示例应用程序(用于名称,地址和电话号码)具有联机和脱机模式。 脱机时,数据驻留在本地持久存储中。 切换到联机模式后,简单的数据同步功能会将本地数据更改同步到服务器。 该应用程序在联机和脱机模式下均支持四种基本的持久存储功能(创建,读取,更新和删除[CRUD])。

应用程序架构概述

回顾一下,首先看一下图1,它显示了Contact Manager的体系结构。 服务器体系结构包含两个与业务服务和数据提供者相对应的servlet。 UI由一个HTML文件和四个JavaScript模块组成,并带有对jQuery库最新版本的外部引用。

在本文中,您将使用基于IndexedDB API的脱机数据库提供程序代替。 具体来说,您将替换localdb.js JavaScript模块。

图1. Contact Manager应用程序架构
该图显示了应用程序体系结构,其中的框显示了客户端体系结构和服务器体系结构下的servlet。

数据模型概述

数据模型由两个数据实体组成:联系和状态(请参见图2)。 联系人表包含实际联系人数据。 状态表包含状态选择列表的字典值。

图2. Contact Manager应用程序数据模型
该图显示了数据模型

IndexedDB API

HTML5规范包含几种持久的存储技术。 IndexedDB是首选HTML5浏览器数据库; 它替换了不推荐使用的WebSQL数据库。

Web浏览器支持和IndexedDB API的实现在Web浏览器中并不总是一致的。 在撰写本文时,Google Chrome 11 +,Mozilla Firefox 4+和Windows®InternetExplorer®10支持IndexedDB。

一个网站可以包含一个或多个用唯一名称标识的数据库。 每个数据库可以包含一个或多个对象存储 。 对象存储类似于关系数据库中的表,因为它由唯一的名称标识并且是记录的集合。 但是,对象存储处理数据存储,访问和查询的方式与关系数据库中表的处理方式不同。 对象存储中的数据与键和值一起存储。 密钥在对象存储中必须唯一,并且可以由密钥生成器指定或生成。

IndexedDB API规范包括对常见数据库结构的支持,例如事务,索引,查询数据和游标。 该规范包括同步和异步API。 同步API旨在在Web Worker中使用。 但是,并非所有的Web浏览器都支持Web Worker和IndexedDB同步API。 异步API使用请求和回调。 所有数据库操作,例如打开数据库,检索数据,查询数据和删除数据,都具有请求API调用。 每个请求都有一个onsuccessonerror回调,分别在操作成功或不成功时调用。 回调为返回的数据提供事件结果参数。

样本Contact Manager应用程序由具有两个对象存储的单个数据库组成。 第一个存储是联系人对象存储,其中包含实际的联系人记录。 第二个对象存储是状态对象存储,其中包含状态选择列表的值。 本文演示了如何使用异步API进行数据访问。

连接到数据库或从数据库断开连接

由于每种Web浏览器实现IndexedDB的方式都略有不同,因此最好创建一个全局变量( localDatabase )并根据不同的Web浏览器实现对其进行初始化。 此全局变量提供对IndexedDB API的引用。

下一步是使用open方法打开数据库。 如果打开数据库请求成功,则调用onsuccess回调。 所有数据库操作代码都应在onsuccess回调中发生。 如果打开数据库时发生错误,则调用onerror回调。 清单1显示了初始化和打开数据库的代码。

清单1.打开一个数据库
var localDatabase = {};

localDatabase.indexedDB = window.indexedDB || window.mozIndexedDB || 
   window.webkitIndexedDB || window.msIndexedDB;
localDatabase.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
localDatabase.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;

console.log('opening local database');
var openRequest = localDatabase.indexedDB.open(dbName);
openRequest.onerror = function(e) {
   console.log("Database error: " + e.target.errorCode);
};
openRequest.onsuccess = function(event) {
   console.log("open database request succeeded ");

   console.log('set db');
   db = openRequest.result;

...
};

要与数据库断开连接,只需调用IndexedDB数据库对象的close方法。

创建对象存储

下一步是在数据库中创建对象存储。 您可以随时创建对象存储,但是创建对象存储后,只能在打开数据库请求的onupgradeneeded回调中修改它。 该回调表明必须升级数据库。

清单2中的代码显示了如何创建两个对象存储。

清单2.创建对象存储
openRequest.onupgradeneeded = function (evt) {   
console.log('creating object stores');
var contactsStore = evt.currentTarget.result.createObjectStore
(contactStore, {keyPath: "id"});
var statesStore = evt.currentTarget.result.createObjectStore
(stateStore, {keyPath: "itemId"});
console.log('object stores created');
};

keyPath标识对象存储库的键字段。

为选择列表创建字典

使用事务创建和修改对象存储中的记录。 IndexedDB API为事务提供了三种模式:

  • readonly —提供对对象存储库的readonly访问。
  • readwrite —提供对对象存储的读写访问。
  • versionchange —除了对对象库的读写访问之外,还提供了创建和删除对象库的功能。

您使用以下语法创建事务:

var transaction = db.transaction("states", "readwrite");

创建事务后,下一步是获取对对象存储的引用。 transaction对象包含objectstores属性,该属性提供对与数据库关联的对象存储的访问。 要获取对states对象存储的引用,请使用以下语句:

var store = transaction.objectStore("states");

连接到服务器后,您希望将所有本地states数据替换为最新的服务器数据。 为此,您必须先清除状态对象存储,然后再使用服务器中的最新值填充状态对象存储。 在对象库上使用clear方法可以做到这一点。

如果成功清除了对象存储,则会调用onsuccess回调。 在此回调中,您现在可以循环浏览并将每个值添加到本地状态对象存储中。 您要添加到对象存储中的数据包含在stateArray字符串数组中。 使用jQuery $.each方法迭代数组。 对于states数组中的每个值,调用put方法将记录添加到状态对象存储中。

清单3中的代码片段显示了如何使用states数组中的值填充对象存储以进行本地和脱机访问:

清单3.将状态数据保存在对象存储中
try {
   console.log('saving local state data');

   var openRequest = localDatabase.indexedDB.open(dbName);

   openRequest.onerror = function(e) {
      console.log("Database error: " + e.target.errorCode);
   };

   openRequest.onsuccess = function(event) {
      db = openRequest.result;

      console.log('opening states store');
      var transaction = db.transaction("states", "readwrite");
      var store = transaction.objectStore("states");                    
     
      var clearReq = store.clear();
      clearReq.onsuccess = function (ev) { 
         console.log('cleared state store');
      
         $.each(stateArray, function(i,item){
            var itemId = generateUUID();
   
            var request = store.put({
               "text": item,
               "itemId" : itemId
            });
   
            request.onsuccess = function(e) {
            };
            
            request.onerror = function(e) {
               console.log(e.value);
            };
         });
      };

      db.close();
   };
}
catch(e){
   console.log(e);
}

现在是时候离线进行添加和更新联系人记录了。

添加和更新联系人

本文使用与第1部分 (将脱机数据存储在localstorage )相同的方法来创建和更新记录。 新记录由ID字段的唯一生成的负数标识。 负数表示该记录是新记录,必须在服务器数据库表中创建。 另外, isDirty标志指示该记录在脱机时已被修改或创建。 使用put方法将记录保存在对象存储中(类似于填充状态对象存储的方式)。 清单4显示了在对象存储中创建和更新记录的完整代码:

清单4.创建和更新联系人记录
openRequest.onsuccess = function(event) {
   db = openRequest.result;
   
   var transaction = db.transaction(contactStore, "readwrite");
   var objectStore = transaction.objectStore(contactStore);
    
   var id = $('#contactId').val();
   var firstName = $('#firstName').val();
   var lastName = $('#lastName').val();
   var street1 = $('#street1').val();
   var street2 = $('#street2').val();
   var city = $('#city').val();
   var zipCode= $('#zipCode').val();
   var state= $('#state').val();;
   
   if (contactId > 0) {
      var getRequest = objectStore.get(parseInt(contactId));
      
      getRequest.onsuccess = function(event)
      {  
         var contact = event.target.result;

         contact.firstName = firstName;
         contact.lastName = lastName;
         contact.street1 = street1;
         contact.street2 = street2;
         contact.city = city;
         contact.zipCode = zipCode;
         contact.isDirty = true;
         contact.lastModifyDate = "";
         contact.isDeleted = false;
         
         var addRequest = objectStore.put(contact);
         
         addRequest.onsuccess = function(event) {
            recordUpdated=true;
         };
   
         addRequest.onerror = function(e) {
            console.log(e.value);
         };
      };
      
      getRequest.onerror = function(e) {
         console.log(e.value);
      };
   } // if update
   else {
      var newContactId = (-1) * Math.floor(Math.random()*100000);

      var lastModifyDate = "";
      var newContact = {
         "timeStamp": "", 
         "id":newContactId,
         "firstName": firstName,
         "lastName": lastName,
         "street1": street1,
         "street2": street2,
         "city": city,
         "zipCode": zipCode,
         "state": state,
         "isDirty":true,
         "lastModifyDate": "",
         "isDeleted":false };
      
      var request = objectStore.put(newContact);
      
      var nextIndex = data.length;
      data[nextIndex] = newContact;
      recordUpdated=true;
   } // if create

接下来,我展示了如何在离线时删除联系人记录。

离线时删除联系人

脱机时删除联系人时,您不想删除记录。 而是,您希望阻止它显示在脱机联系人列表中,并指示该记录已删除。 您可以通过使用isDeleted标志(类似于本系列第1部分中描述的方法)来实现。 isDirty标志也设置为true以指示该记录已脱机修改。 在对象存储中更改记录后,刷新联系人列表以从列表中删除记录(不显示isDeleted标志设置为true记录)。 清单5中的代码显示了如何完成此任务。

清单5.删除联系人记录
try {
   console.log('deleting local contact');
   
   var openRequest = localDatabase.indexedDB.open(dbName);
   console.log('after open');
   openRequest.onerror = function(e) {
      console.log("Database error: " + e.target.errorCode);
   };
   openRequest.onsuccess = function(event) {
      db = openRequest.result;
   
      console.log('opening contacts store');
      var transaction = db.transaction(contactStore, "readwrite");
      var store = transaction.objectStore(contactStore);

      var getRequest = store.get(contactId);  
      getRequest.onsuccess = function (ev) {
         var item = getRequest.result;
         item.isDeleted=true;
         item.isDirty=true;

         var request = store.put(item);

         request.onsuccess = function(e) {
            alert('Contact deleted');
            loadOfflineContacts();
         };

         request.onerror = function(e) {
            console.log(e.value);
         };
      }

      getRequest.onerror = function(e) {
         console.log(e.value);
      };

      db.close();
   };              

   loadOfflineContacts();
}
catch(e){
   console.log(e);
}

查询联系人

要从联系人对象存储中检索联系人记录,请使用IndexedDB游标。 就像关系数据库中的数据库游标一样,IndexedDB游标提供了一种遍历对象存储中记录的方式。 在遍历记录时,您将构建一个包含联系人记录的数组。 isDeleted标志设置为true任何记录都将被忽略。 清单6显示了如何使用联系人对象存储库中包含的数据构建联系人数组。

清单6.查询联系人
var data = new Array();
...
var cursorRequest = objectStore.openCursor();
cursorRequest.onsuccess = function(evt) {  
   var cursor = evt.target.result;  
   if (cursor) {  
      if (!cursor.value.isDeleted) {
         var newContact = {
            "timeStamp":cursor.value.timeStamp,
            "id":cursor.value.id,
            "firstName": cursor.value.firstName,
            "lastName": cursor.value.lastName,
            "street1": cursor.value.street1,
            "street2": cursor.value.street2,
            "city": cursor.value.city,
            "zipCode": cursor.value.zipCode,
            "state": cursor.value.state,
            "lastModifyDate": cursor.value.lastModifyDate,
            "isDeleted": cursor.value.isDeleted,
            "isDirty": cursor.value.isDirty
         };
         //console.log('adding ' + newContact.toString());
         //console.log("adding contact to array: " + data.length);
         data[data.length]= newContact;
      }
   
      cursor.continue();
   } // more records
   else {
      displayContactData(data);    
   } // no more records
};  // open cursor

接下来,我演示了一种简单的算法和方法,用于将脱机添加和修改与服务器同步。

与服务器同步本地数据

当您在线工作时,所有CRUD操作都使用Servlet,并且服务器数据库会立即更新。 本地(IndexedDB)数据库也会通过在线数据库更改进行更新,以确保最新数据始终在线或离线可用。

脱机时,所有CRUD操作都会更新IndexedDB数据库中的数据。 与服务器重新连接后:

  • 在本地数据库中创建的所有记录都将保留到服务器。
  • 在本地数据库中修改的所有记录都会在服务器上更新。
  • 在本地数据库中删除的所有记录都将在服务器上删除。

清单7中的代码显示了完整的同步方法。 第1部分中描述的相同的在线功能用于创建,更新和删除操作。

第一步是使用游标遍历联系人对象存储中的记录,并构建一个包含所有需要发布到服务器的记录的数组。 在本地更新或创建的记录的isDirty属性设置为true 。 如果保存操作的唯一记录ID为负(即未由MySQL数据库分配),则将其标识为新操作。 使用isDeleted属性标记在本地删除的记录。

当您拥有包含所有需要发布到服务器的所有记录的数组时,请使用jQuery $.each方法遍历该数组并将每次更改都发布到服务器。

最后,数据同步方法完成后,它将使用服务器中的最新数据刷新本地联系人对象存储。 这包括您(和其他用户)离线时所做的任何更改:

清单7.与服务器同步本地数据
...
cursorRequest.onsuccess = function(evt) {  
var cursor = evt.target.result;  

if (cursor) {  
   var isDirty = cursor.value.isDirty;
   var curId = cursor.value.id;
   console.log(curId + ' isDirty = ' + isDirty);
   if (isDirty) {
      var newContact = {
         "timeStamp":cursor.value.timeStamp,
         "id":curId,
         "firstName": cursor.value.firstName,
         "lastName": cursor.value.lastName,
         "street1": cursor.value.street1,
         "street2": cursor.value.street2,
         "city": cursor.value.city,
         "zipCode": cursor.value.zipCode,
         "state": cursor.value.state,
         "lastModifyDate": cursor.value.lastModifyDate,
         "isDeleted": cursor.value.isDeleted,
         "isDirty": cursor.value.isDirty
      };
      //console.log('adding ' + newContact.toString());
      //console.log("adding contact to array: " + data.length);
      data[data.length]= newContact;
   }

   cursor.continue();
} // more records
else {
   console.log("no more records");

   console.log('number of modified records: ' + data.length);
   var recordsUpdated = 0;
   var recordsCreated = 0;
   var recordsDeleted = 0;
   
   $.each(data, function(i,item){
      console.log("processing record " + item.id);
      if (item.isDeleted) {
         deleteOnlineContact(item.id, true);
         recordsDeleted++;
      }
      else if (item.isDirty && !item.isDeleted) {
         $('input[name="contactId"]')[0].value = item.id;
         $('input[name="firstName"]')[0].value = item.firstName;
         $('input[name="lastName"]')[0].value = item.lastName;
         $('input[name="street1"]')[0].value = item.street1;
         $('input[name="street2"]')[0].value = item.street2;
         $('input[name="city"]')[0].value = item.city;
         $('select[name="state"]')[0].value = item.state;
         $('input[name="zipCode"]')[0].value = item.zipCode;

         var dataString = $("#editContactForm").serialize();
         postEditedContact(dataString, true);
         if (item.id > 0) {
            recordsUpdated++;
         }
         else {
            recordsCreated++;
         }   
      }
   });

   var msg = "Synchronization Summary\n\tRecords Updated: " + recordsUpdated 
      + "\n\tRecords Created: " + recordsCreated 
      + "\n\tRecords Deleted: " + recordsDeleted;
   alert(msg);

结论

本文以第1部分中描述的基础为基础。 它为联机和脱机支持以及维护一致的用户体验使用相同的模式。 它引入了IndexedDB API,这是一种数据同步算法,用于同步记录的脱机创建,删除和修改。 由于该代码没有强大的错误处理和冲突解决方案(当同一记录在本地由另一用户在服务器上修改时),因此该代码无法投入生产。 但是,它确实为将来的工作提供了良好的基础。


翻译自: https://www.ibm.com/developerworks/web/library/wa-html5db-pt2/index.html

indexeddb

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值