ionic app中如何使用PouchDB+SQLite作为本地存储

注:本篇乃是译文,难免会有疏漏,欢迎大家批评指正,大家也可挪步原文

我最近在寻找一种在ionic app中作为本地存储的解决方案,就我所查找的资料来看,最流行的方式是使用SQLite数据库。

然而,跟随别人探寻PouchDB的脚步发现PouchDB能够实现本地存储并与服务器端同步,但是对于特定的app来说我并需要服务器端。而且PouchDB可以不使用SQL语句的方式进行数据库存储,而我本身又有数据库经验,所以我想我应该使用SQLite.

随着继续深入了解,在ionic app的开发中使用PouchDB要比SQLite拥有更好的性能,开发也更加容易,即使只是用PouchDB作为本地存储。

注:也可以使用LokiJS代替PouchDB,详情可参考我的另外一篇译文ionic怎样使用LokiJS作为本地存储

本篇文章的源码可在Github上找到。

关于PouchDB

PouchDB是一个开源的JavaScript库,在浏览器中使用IndexedDB或WebSQL存储数据。是从Apache CouchDB衍生而来,并且允许本地数据和CouchDB服务器进行数据同步。

IndexedDB或WebSQL均有存储空间的限制,如果想要在ionic mobile app中获得“无限制”存储,最好还是使用SQLite。如果安装了Cordova SQLite插件,那么PouchDB将会自动使用SQLite数据库。

注:SQLite的速度确实要比IndexedDB或WebSQL的速度慢。

安装相关库

为了在我们的app中让PouchDB能够使用SQLite,我们需要安装:

  • SQLite Plugin for Cordova
  • PouchDB
    安装SQLite Plugin和其他Cordova Plugin一样,在ionic app的目录下,终端执行:
$ cordova plugin add io.litehelpers.cordova.sqlitestorage

安装PouchDB,这里我使用bower,大家也可使用其他方式:

$ bower install pouchdb

安装完成PouchDB,下一步自然是在index.html中引入js文件:

<script src="lib/pouchdb/dist/pouchdb.min.js"></script>

这样就完成了相关库的安装,下面就是写代码了。

我们要做什么?

本文中,我们是要完成一个生日记录的app,具有增加、删除、更新、查看朋友们生日的功能:

构造数据库相关操作的service

第一步,我们需要构造一个service封装PouchDB的功能调用:

angular.module('starter').factory('BirthdayService',['$q',BirthdayService]);

function BirthdayService($q) {  
    var _db;    

    // We'll need this later.
    var _birthdays;

    return {
        initDB: initDB,// We'll add these later.
        getAllBirthdays: getAllBirthdays,addBirthday: addBirthday,updateBirthday: updateBirthday,deleteBirthday: deleteBirthday
    };

    function initDB() {
        // Creates the database or opens if it already exists
        _db = new PouchDB('birthdays',{adapter: 'websql'});
    };
}

initDB函数当数据库不存在时会新建一个数据库,当数据库存在时打开数据库。

可以看到,上面的代码只实现了initDB函数,下面实现其他功能函数:

addBirthday函数

addBirthday函数用来向我们的数据库插入一条生日信息:

function addBirthday(birthday) {  
      return $q.when(_db.post(birthday));
};

咦,怎么没有insert语句呢?在PouchDB中,birthday对象会被简单处理成JSON,并存储在数据库中。

插入数据有2种方式,一种是像上面那样使用post方法,一种是使用put方法。使用post方法,PouchDB会自动帮你生成_id,而使用put方法,需要自己手动添加_id。

看到这,大家可能有些疑惑,为什么要将_db.post封装到$q的promise中。我会在下文解释。

updateBirthday函数

function updateBirthday(birthday) {  
    return $q.when(_db.put(birthday));
};

deleteBirthday函数

function deleteBirthday(birthday) {  
    return $q.when(_db.remove(birthday));
};

getAllBirthdays函数

function getAllBirthdays() {  
    if (!_birthdays) {
       return $q.when(_db.allDocs({ include_docs: true}))
            .then(function(docs) {

                // Each row has a .doc object and we just want to send an 
                // array of birthday objects back to the calling controller,
                // so let's map the array to contain just the .doc objects.
                _birthdays = docs.rows.map(function(row) {
                    // Dates are not automatically converted from a string.
                    row.doc.Date = new Date(row.doc.Date);
                    return row.doc;
                });

                // Listen for changes on the database.
                _db.changes({ live: true,since: 'now',include_docs: true})
                   .on('change',onDatabaseChange);

                return _birthdays;
            });
    } else {
        // Return cached data as a promise
        return $q.when(_birthdays);
    }
};

这里使用allDocs 函数获得数据库内所有的birthday对象,并返回一个数组。我不想让调用这个service的controller能够获取到PouchDB或docs的所有信息,所以我将rows数组映射成为一个只包含row.doc对象的新数组。

可以看到这里对row.doc.Date做了转换,转换成Date对象,因为不幸的是JSON中的日期不能自动转换回Date对象。

我还将结果保存了一份到_birthdays数组中作为缓存使用,这样我只需要在app开始时访问一次数据库。

但是,这就有个问题,要怎样保持_birthdays缓存和数据库内容的同步呢?下面的onDatabaseChange 函数就是为此而生的:

function onDatabaseChange(change) {  
    var index = findIndex(_birthdays,change.id);
    var birthday = _birthdays[index];

    if (change.deleted) {
        if (birthday) {
            _birthdays.splice(index,1); // delete
        }
    } else {
        if (birthday && birthday._id === change.id) {
            _birthdays[index] = change.doc; // update
        } else {
            _birthdays.splice(index,0,change.doc) // insert
        }
    }
}

// Binary search,the array is by default sorted by _id.
function findIndex(array,id) {  
    var low = 0,high = array.length,mid;
    while (low < high) {
    mid = (low + high) >>> 1;
    array[mid]._id < id ? low = mid + 1 : high = mid
    }
    return low;
}

onDatabaseChange 函数使你能够在数据库变化时更新_birthdays缓存。onDatabaseChange 函数需要传入一个对象作为参数,此参数包含了id值和doc对象内的真实数据。如果此id没有在_birthdays数组中找到,那就意味着我们我们要新增加一条生日记录了,否则就意味着我们要进行updatedelete操作了。

为什么使用$q

上文留下了一个问题:为什么使用$q封装数据库操作?

PouchDB所有的数据库操作都是异步的,并且使用promise。不幸的是当promise被resolve后,Angular并不知道需要去更新UI,然而,当使用$q封装相关操作后,神奇的事情就发生了,Angular就知道应该去更新UI了。

创建UI

目前为止,我们已经创建了service承担大部分工作了,现在开始UI的设计。

首先,我们添加一个controller:OverviewController调用birthdayService.initDB函数初始化数据库,注意操作应该在$ionicPlatform.ready(表示设备已经就绪)内执行:

angular.module('starter').controller('OverviewController',['$scope','$ionicModal','$ionicPlatform','BirthdayService',OverviewController]);

function OverviewController($scope,$ionicModal,$ionicPlatform,birthdayService) {  
    var vm = this;

    // Initialize the database.
    $ionicPlatform.ready(function() {
        birthdayService.initDB();

        // Get all birthday records from the database.
        birthdayService.getAllBirthdays().then(function(birthdays) {
            vm.birthdays = birthdays;
        });
    });

    // Initialize the modal view.
    $ionicModal.fromTemplateUrl('add-or-edit-birthday.html',{
        scope: $scope,animation: 'slide-in-up'
    }).then(function(modal) {
        $scope.modal = modal;
    });

    vm.showAddBirthdayModal = function() {
        $scope.birthday = {};
        $scope.action = 'Add';
        $scope.isAdd = true;
        $scope.modal.show();           
    };

    vm.showEditBirthdayModal = function(birthday) {
        $scope.birthday = birthday;
        $scope.action = 'Edit';
        $scope.isAdd = false;          
        $scope.modal.show();
    };

    $scope.saveBirthday = function() {
        if ($scope.isAdd) {
            birthdayService.addBirthday($scope.birthday);              
        } else {
            birthdayService.updateBirthday($scope.birthday);               
        }                       
        $scope.modal.hide();
    };

    $scope.deleteBirthday = function() {
        birthdayService.deleteBirthday($scope.birthday);           
        $scope.modal.hide();
    };

    $scope.$on('$destroy',function() {
        $scope.modal.remove(); 
    });

    return vm;
}

最后,在index.html中创建UI,这里我们使用$ionicModal弹出“增加生日”和“编辑生日”的界面:

<body ng-app="starter">  
  <ion-pane ng-controller="OverviewController as vm">
    <ion-header-bar class="bar-stable">
      <h1 class="title">Birthdays</h1>
      <div class="buttons">
        <button ng-click="vm.showAddBirthdayModal()" class="button button-icon icon ion-plus"></button>
      </div>
    </ion-header-bar>
    <ion-content>        
      <ion-list>
        <ion-item ng-repeat="b in vm.birthdays" ng-click="vm.showEditBirthdayModal(b)">
          <div style="float: left">{{ b.Name }}</div>
          <div style="float: right">{{ b.Date | date:"dd MMMM yyyy" }}</div>
        </ion-item>
      </ion-list>
    </ion-content>
  </ion-pane>

  <script id="add-or-edit-birthday.html" type="text/ng-template">
    <ion-modal-view>
      <ion-header-bar>
        <h1 class="title">{{ action }} Birthday</h1> 
        <div class="buttons">
        <button ng-hide="isAdd" ng-click="deleteBirthday()" class="button button-icon icon ion-trash-a"></button>
        </div>
      </ion-header-bar>
      <ion-content>
        <div class="list list-inset">
          <label class="item item-input">
          <input type="text" placeholder="Name" ng-model="birthday.Name">
          </label>
          <label class="item item-input">
          <input type="date" placeholder="Birthday" ng-model="birthday.Date">
          </label>
        </div>
        <div class="padding">
          <button ng-click="saveBirthday()" class="button button-block button-positive activated">Save</button>
        </div>
      </ion-content>
    </ion-modal-view>
  </script> 
</body>

检查数据库

Chrome上有个PouchDB Inspector扩展可以方便的在Chrome Developer Tools内查看数据库的内容:

注:当使用

$ ionic serve --lab

在浏览器中查看效果时,不能使用PouchDB Inspector。因为它会使用iframe展示Android和IOS的UI效果,而PouchDB Inspector连接PouchDB是通过window.PouchDB的,当window对象处于iframe标签内就不能使用window.PouchDB了。

可能存在的问题

注意,当你在桌面浏览器上测试此app时,PouchDB使用IndexDB adapter或WebSQL adapter,具体使用哪个取决于你的浏览器,可以使用如下方式获知使用的是那个adapter:

var db = new PouchDB('birthdays');  
console.log(db.adapter);

在手机上测试时,即使使用SQLite,上面的代码可能返回结果也是websql,为了证实使用的确实是SQLite,需要使用如下代码(StackOverflow问答):

var db = new PouchDB('birthdays');  
db.info().then(console.log.bind(console));

这段代码会返回一个对象,其中sqlite_plugin属性为true或false。

删除数据库

var db = new PouchDB('birthdays');  
db.destroy().then(function() { console.log('ALL YOUR BASE ARE BELONG TO US') });

理论上来说,也可以使用PouchDB Inspector删除数据库,不过因为某些原因,我没有测试通过。

结语

可以看出,使用PouchDB还是很方便的,功能也很强大。

推荐一款个人使用了半年的理财产品:创建了6年的挖财,新人收益36%,7天18%,1年10%,注册送308元券

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


SQLite架构简单,又有Json计算能力,有时会承担Json文件/RESTful的计算功能,但SQLite不能直接解析Json文件/RESTful,需要用Java代码硬写,或借助第三方类库,最后再拼成insert语句插入数据表,代码非常繁琐,这里就不展示了。参考前面的代码可知,入库的过程比较麻烦,不能只用SQL,还要借助Java或命令行。SPL是现代的数据计算语言,属于简化的面向对象的语言风格,有对象的概念,可以用点号访问属性并进行多步骤计算,但没有继承重载这些内容,不算彻底的面向对象语言。...
使用Python操作内置数据库SQLite以及MySQL数据库。
破解微信数据库密码,用python导出微信聊天记录
(Unity)SQLite 是一个软件库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是在世界上最广泛部署的 SQL 数据库引擎。SQLite 源代码不受版权限制。本教程将告诉您如何使用 SQLite 编程,并让你迅速上手。.................................
安卓开发,利用SQLite实现登陆注册功能
相比大多数数据库而言,具有等优势,广泛应用于、等领域。
有时候,一个项目只有一个数据库,比如只有SQLite,或者MySQL数据库,那么我们只需要使用一个固定的数据库即可。但是一个项目如果写好了,有多个用户使用,但是多个用户使用不同的数据库,这个时候,我们就需要把软件设计成可以连接多个数据库的模式,用什么数据库,就配置什么数据库即可。4.Users实体类,这个实体类要和数据库一样的,形成一一对应的关系。11.Sqlite数据库,需要在代码里面创建数据库,建立表,再建立数据。8.我们开启MySQL数据库,然后进行调试,看程序的结果。2.安装SqlSugar。
基于Android的背单词软件,功能强大完整。
SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统。说白了就是使用起来轻便简单,
Android的简单购物车案例
SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库...
Qt设计较为美观好看的登录注册界面(包含SQLite数据库以及TCP通信的应用)
SQLite是用C语言开发的跨平台小型数据库,可嵌入其他开发语言,也可在单机执行。SPL是用Java开发的跨平台的数据计算语言,可嵌入Java,可在单机执行,可以数据计算服务的形式被远程调用。两者的代码都是解释执行的。...
新建库.openDATA_BASE;新建表createtableLIST_NAME(DATA);语法:NAME关键字...<用逗号分割>删除表droptableNAME;查看表.schema查看表信息新建数据insertintoLIST_NAMEvalues();语法:CLASS,PARAMETER...,CLASS是类别,PARAMETER是参数<用逗号分割新建的
importsqlite3classDemo01:def__init__(self):self.conn=sqlite3.connect("sql_demo_001.db")self.cursor1=self.conn.cursor()self.cursor1.execute("select*fromtable_001wherename=?andid=?",('ssss&#0
 在客户端配置文件<configuration>节点下,添加:<connectionStrings>      <add name="localdb" connectionString="Data Source=config/local.db;Version=3;UseUTF16Encoding=True;" providerName="System.Data.SQLite.SQLiteFactory"/&g
提到锁就不得不说到死锁的问题,而SQLite也可能出现死锁。下面举个例子:连接1:BEGIN(UNLOCKED)连接1:SELECT...(SHARED)连接1:INSERT...(RESERVED)连接2:BEGIN(UNLOCKED)连接2:SELECT...(SHARED)连接1:COMMIT(PENDING,尝试获取EXCLUSIVE锁,但还有SHARED锁未释放,返回SQLITE_BUSY)连接2:INSERT...
SQLite是一种嵌入式数据库,它的数据库就是一个文件。由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成。Python就内置了SQLite3,所以,在Python中使用SQLite,不需要安装任何东西,直接使用。在使用SQLite前,我们先要搞清楚几个概念:表
设计思想————首先要确定有几个页面、和每个页面的大致布局由于是入门,我也是学习了不是很长的时间,所以项目比较low。。。。第一个页面,也就是打开APP的首页面:今天这个博客,先实现添加功能!:首先对主界面进行布局:其中activity_main.xml的代码为<?xmlversion="1.0"encoding="