我搭建了个人博客主页, 欢迎访问: blog.joelzho.com
CSDN 的 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;
}
}