Flutter 中的 SQLite

我搭建了个人博客主页, 欢迎访问: blog.joelzho.com

CSDN 的 markdown 编辑器 好像不支持 dart 语言高亮。
知道的朋友可以教教我 dart 高亮的 代码块关键字是什么.

在iOS 中, 我们可以使用sqlite3的C语言接口或者是CoreData 接口来操作SQLite数据库.
在Android 中我们可以使用android.database.sqlite.SQLiteDatabase来操作SQLite.
那么, 在Flutter 应用中是如何操作的呢?

注意:

  1. 有些难以形容的东西我会说它和Java中的某个东西类似;
  2. 本人对 Dart Lang 理解并不深;
  3. 本人并不能非常熟练的使用 Flutter;
  4. 本人对 method 和 function 统称为 函数;

一: 导入依赖

1.库名字

在 Flutter 中, 具有和 原生iOS 或者 Android 开发中一样的开发接口, 它就是 sqflite.
这个库的名字真有意思哈, 就和redis 的Java 操作接口 jedis的名字一样有艺术.

2.如何找到它

与其他我们使用大部分依赖一样, 我们可以在 pub.dev/flutter 这个网站找到它, 并查看安装方法和一些示例.

3.添加到工程依赖

你需要从 pub.dev 查看并安装它的最新版本, 以适配你目前使用的 Flutter & Dart SDK 版本,
当前我使用的是 Flutter v1.6.3, 最新的 sqflite 版本是 1.1.5.

那么, 需要在项目下的 pubspec.yaml 文件中的 dependencies 节点下添加依赖.
添加后的示例如下:

dependencies:
  flutter: 
    sdk: flutter
  sqflite: ^1.1.5

注意: yaml 是一种很神奇的配置文件, 建议你手动配置, 而不要复制本文中的配置, 有可能由于系统环境不一致产生一些不必要的错误.

4. 导入接口文件

在你的dart 源码文件顶部添加以下代码

import 'package:sqflite/sqflite.dart'

二: 部分API 接口

总所周知, Dart 接口的参数一般非常多, 因为它们都使用 options 概念来完成初始化.
所以, 本文对接口描述时不会将所有参数都列出, 而是仅仅列出那些我关注过的参数.

1.创建数据库

使用的函数名字是: openDatabase,
它是一个异步函数, 返回的是一个 用于操作数据库的 Database对象.

代码示例:

Future<DataBase> initDataBase() async {
    return await openDatabase(path, onOpen: _onDatabaseCreate, );
}

在上面的代码中, 第一个参数是一个字符串类型的路径, 例如: xx/mydb.db.
第二个参数是一个事件监听, 也就是说当数据库创建的时候要做的事情, 例如创建表.
onOpen 需要的回调函数原型为:

Future onOpen(Database db)

2. 创建表

在上面, 我们调用 openDatabase 创建数据库时传入了一个 onOpen 的事件回调函数,
那么接下来我们就在这个函数中来创建一个表.
我传入的函数是 _onDatabaseCreate, 然后我的实现如下:

Future _onDatabaseCreate(Database db) async {
  await db.execute(
    '''
    CREATE TABLE IF NOT EXISTS `tableName` (
      `colName1` VARCHAR(1024) PRIMARY KEY,
      `colName2` TEXT NOT NULL
    )
    '''
  );
}

可以看出, 创建表我们使用的是 Database 的 execute 函数, 它接收一个字符串sql作为参数.
这个和其他语言的接口没太大区别, 有些不同的是这个函数不返回任何东西, 也就是 Future<void>,
执行失败会报错.

值得一提的是, execute 函数并非只接收一个参数, 它允许你的第一个参数是 预编译SQL语句, 第二个参数是一个 List, 表示填充预编译SQL里面的占位符.

函数原型为:

Future<void> execute(String sql, [List<dynamic> args]);

3. 插入数据与事务

sqflite 接口中, 对 CRUD 的各项操作不仅提供的原生SQL 的支持, 还提供了一些更简单的操作函数,
作者将这类更便捷的操作函数称作 helper function, 而原生SQL支持的函数叫做 raw function.

其实 helper function 就是允许你传入一些其他数据结构, 然后帮你生成 raw SQL 并执行.
在作者提供的函数中, 以 raw 开头的表示原生SQL函数, 没有额外声明的即是 便捷函数(helper function).

例如 rawInsert 表示执行原生SQL 插入, insert 是一个帮助函数, 可以传入其他结构进行插入.

下面列出了 rawInsertinsert 函数的使用方法.

rawInsert

Future<int> insertOne (String key, String value) async {
  return await _database.rawInsert(
      'INSERT INTO `tableName`(`colName1`, `colName2`) VALUES(?, ?)',
      [key, value]
  );
}

_database 是一个 Database 对象.

insert

class KvPair {
	String colName1;
	String colName2;

	Map<String, dynamic> toMap() {
		var mp = Map();
		mp['colName1'] = colName1;
		mp['colName2'] = colName2;
	}
}

Future<int> insertMany( List<KvPair> datas ) {
	return await _database.transaction( (txn) async {
      for (var data in datas) {
      	txn.insert('tableName', data.toMap() )
      }
  } );
}

可以看出, insert 方法中我们不用写sql 了, 而是传入一个map, 它会自己生成 插入语句.

在上面的代码中同时展示了 sqflite 中的事务,
启动事务的函数为 transaction, 它的函数原型为:

Future<T> transaction (Future<T> action(Transcation txn), {bool exclusive});

第一个参数是一个用于提供事务操作的函数, 第二个是 bool 类型的 参数, 表示是否独占连接资源.

action 函数会传入一个 Transaction 用于提供执行CRUD的SQL,
Transaction 和 Database 提供的 CRUD 函数是一致的,
值得注意的是, 不要的 action 函数中调用 启动事务的 Database 对象, 否则会造成死锁.

4. 查询

rawQuery

Future<Map<String, String>> queryAll(List<String> keys) async {
  var size = keys.length;
  var sql = 'SELECT * FROM `tableName` WHERE `colName1` IN (';
  for (var i = 0; i < size; i++) {
    sql += '?,';
  }
  sql = sql.substring(0, sql.length - 1);
  sql += ')';

  var maps = await _database.rawQuery(sql, keys);

  if (maps.length > 0) {
    var map = Map();
    for (var mp in maps) {
      if (mp.containsKey('colName1') &&
          map.containsKey('colName2')) {
        map[mp['colName1']] = mp['colName2'];
      }
    }
    return map;
  }
  return null;
}

query

Future<String> query(String key) async {
  var maps = await _database.query('tableName',
      columns: ['colName2'],
      where: '`colName1` = ?',
      whereArgs: [key]
  );
  if (maps.length > 0 && maps[0].containsKey('colName2')) {
    return maps[0]['colName2'];
  }
  return null;
}

query 和 rawQuery 返回的都是 List<Map<String, dymanic>>,
其实就和 Java 中的 ResultSet 差不多.

query 函数的这种风格就和 Hibernate 的 Criteria 差不多.

三: 结束

这里就不再演示其他API 的使用方法了, 用法和上面提到的函数都差不多.
下面是一些参考链接:

  1. sqflite pub.dev 地址:
    https://pub.dev/packages/sqflite

  2. sqflite 源码地址:
    https://github.com/tekartik/sqflite

  3. sqflite API 文档:
    https://pub.dev/documentation/sqflite/latest/sqflite/sqflite-library.html

四: 一个用于存储字符串键值对的sqflite示例

以下代码是用vim 盲打出来的, 没有进行编译或者测试可用性, 仅做参考.

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';


class AppCookie {
  static const TABLE = 'application_local_cookie';
  static const COL_KEY = 'key';
  static const COL_VAL = 'value';

  Future _onDatabaseCreate(Database db) async {
    await db.execute(
      '''
      CREATE TABLE IF NOT EXISTS `$TABLE` (
        `$COL_KEY` VARCHAR(1024) PRIMARY KEY,
        `$COL_VAL` TEXT NOT NULL
      )
      '''
    );
  }

  Future<Database> _initDatabase() async {
    var docDir = await getApplicationDocumentsDirectory();
    var path = join(docDir.path, _dbName);
    return await openDatabase(path,
        onOpen: _onDatabaseCreate,
    );
  }

  AppCookie(String dbName) {
    if (dbName == null) {
      _dbName = 'cookie.db';
    } else {
      _dbName = dbName;
    }
    _initDatabase().then((db){
      _database = db;
    });
  }

  Future<bool> init() async {
    _database = await _initDatabase();
    if (_database != null) {
      return true;
    }
    return false;
  }

  // SQLite database instance
  Database _database;

  // SQLite database name
  String _dbName;
  String get dbName {
    return _dbName;
  }

  Future<int> put (String key, String value) async {
    return await _database.rawInsert(
        'REPLACE INTO `$TABLE`(`$COL_KEY`, `$COL_VAL`) VALUES(?, ?)',
        [key, value]);
  }

  Future<int> putAll (Map<String, String> rows) async {
    return await _database.transaction((txn) async {
      rows.forEach((key, value){
        txn.rawInsert(
            'REPLACE INTO `$TABLE`(`$COL_KEY`, `$COL_VAL`) VALUES(?, ?)',
            [key, value]);
      });
    });
  }

  Future<int> erase(String key) async {
    return await _database.delete(TABLE, where: '`$COL_KEY` = ?', whereArgs: [key]);
  }
  Future<int> eraseAll(List<String> keys) async {
    return await _database.transaction((txn) async {
      for (var key in keys) {
        txn.delete(TABLE, where: '`$COL_KEY` = ?', whereArgs: [key]);
      }
    });
  }

  Future<int> queryRowCount() async {
    return Sqflite.firstIntValue(await _database.rawQuery('SELECT COUNT(*) FROM `$TABLE`'));
  }

  Future<String> query(String key) async {
    var maps = await _database.query(TABLE,
        columns: [COL_VAL],
        where: '`$COL_KEY` = ?',
        whereArgs: [key]
    );
    if (maps.length > 0 && maps[0].containsKey(COL_VAL)) {
      return maps[0][COL_VAL];
    }
    return null;
  }
  Future<Map<String, String>> queryAll(List<String> keys) async {
    var size = keys.length;
    var sql = 'SELECT * FROM `$TABLE` WHERE `$COL_KEY` IN (';
    for (var i = 0; i < size; i++) {
      sql += '?,';
    }
    sql = sql.substring(0, sql.length - 1);
    sql += ')';

    var maps = await _database.rawQuery(sql, keys);

    if (maps.length > 0) {
      var map = Map();
      for (var mp in maps) {
        if (mp.containsKey(COL_KEY) &&
            map.containsKey(COL_VAL)) {
          map[mp[COL_KEY]] = mp[COL_VAL];
        }
      }
      return map;
    }
    return null;
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值