Flutter 中的 SQLite

    xiaoxiao2023-11-09  154

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

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

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

    注意:

    有些难以形容的东西我会说它和Java中的某个东西类似;本人对 Dart Lang 理解并不深;本人并不能非常熟练的使用 Flutter;本人对 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 是一个帮助函数, 可以传入其他结构进行插入.

    下面列出了 rawInsert 和 insert 函数的使用方法.

    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 的使用方法了, 用法和上面提到的函数都差不多. 下面是一些参考链接:

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

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

    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; } }
    最新回复(0)