sqflite是Flutter的SQLite插件,在App端可以高效的存儲和處理數據庫數據,官方地址:pub.flutter-io.cn/packages/sq…。html
關於SQLite的學習,推薦 菜 鳥 教 程 的 SQLite 教 程 。sql
SQLite的特色:數據庫
sqflite全稱,個人理解是Structured Query Flutter Language Lite,即用於Flutter的輕量級結構化查詢語言。若是理解有誤,請及時更正,萬分謝謝!json
一塊兒看一下sqflite官方介紹:bash
用於Flutter的SQLite插件,支持iOS、Android、MacOS.服務器
當前sqflite版本:1.2.0async
關於App端的持久化存儲,用的比較多有shared_preferences、數據庫存儲、文件存儲。post
若是你對英文比較自信,能夠嘗試讀這篇文章 Datatypes In SQLite Version 3.學習
SQLite並無對值進行類型檢查,即存儲INTEGER類型的列是能夠存儲TEXT類型的,可是當咱們解析查詢的結果進行映射時,會報類型異常的。因此仍是要避免存儲類型不一致的數據。測試
sqflite支持5種數據類型:NULL, INTEGER, REAL, TEXT, BLOB.
NULL
某一列不存儲數據的時候,默認值是NULL.
INTEGER
dart中的int類型,值的範圍是-2^63 到 2^63 - 1
REAL
dart中的num類型,即int和double類型
TEXT
dart中的String類型
BLOB
dart中的Uint8List類型,雖然可以存儲List< int >,但官方並不建議,由於轉化比較慢。
若是咱們須要存儲其餘類型,好比bool,DateTime,List< String >等數據,須要咱們自行處理,每一個人或許都有本身獨特的方法,但願您可以提出一些建議。咱們能夠經過封裝實體類和解析類,在外部代碼看來,就是實現了存儲bool,DateTime,List< String >等這些類型。
bool
存儲INTEGER類型,0爲false,1爲true.
DateTime
存儲INTEGER類型,一列數據的建立時間和更新時間,通常是比較重要的。固然還有其餘的,好比一個訂單的付款時間、發貨時間、取消時間等不少的時間信息。若是存儲TEXT類型,程序若是支持多種語言的話,仍是不方便的。
List
存儲TEXT類型,咱們能夠根據特殊的分隔符,把數據組合成String存儲到數據庫。而後根據String的split解析成List< String >。仍是有不少須要注意的,好比List的元素中必定不能包含定義的分隔符。對List的某一個Item修改比較麻煩,只能總體覆蓋List。
Map、json、實體類
存儲TEXT類型,通常我使用實體類的toMap方法把實體類轉換成Map, 經過jsonEncode把實體類轉換成String,反過來,利用jsonDecode把String轉換成Map,經過實體類的fromMap轉換成實體類。
根據數據庫的名稱和版本號,Open數據庫。
import 'package:sqflite/sqflite.dart';
String databasesPath = await getDatabasesPath();
// Database Path: /data/user/0/com.package.name/databases
String path = join(databasesPath, 'db_name.db');
// Path: /data/user/0/com.package.name/databases/db_name.db
Database database = await openDatabase(
path,
version: VERSION,
onCreate: (Database db, int version) async {
// 表格建立等初始化操做
},
onUpgrade: (Database db, int oldVersion, int newVersion) async {
// 數據庫升級
},
);
複製代碼
await deleteDatabase(path);
複製代碼
await database.close();
複製代碼
官方的使用文檔中,表格是按需建立的,但我在使用過程當中遇到一個麻煩,關於數據庫的升級問題,由於有些Table,用戶可能並未觸及到,在更新表格結構的時候,須要首先判斷Table是否存在等操做。因此,我在建立數據庫的時候,選擇建立全部好表格(通常狀況下,App端並無太多表格,我寫的項目中最多的一個有11個表格。)。
只須要把openDatabase的參數version加1,程序在打開的時候,會自動調用openDatabase的onUpgrade方法。因此咱們須要在onUpgrade方法中,執行數據庫的升級操做。最霸道的作法是,Drop全部的表格,而後Create最新的表格(會不會被領導打,我就不清楚了)。
使用sqflite,就必需要對SQL語句比較熟悉。對數據庫的ORM封裝,可使用插件庫 sqfentity。
database.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)');
複製代碼
database.execute('DROP table test');
複製代碼
database.execute('DELETE FROM test');
複製代碼
database.execute('ALTER TABLE test RENAME TO test_1');
複製代碼
database.execute('ALTER TABLE test ADD age integer');
複製代碼
database.execute('ALTER TABLE test DROP COLUMN age');
複製代碼
database.execute('ALTER TABLE test ALTER COLUMN value TEXT');
複製代碼
返回值須要注意下:Returns the last inserted record id(返回最後插入的記錄ID).
int id = await database.insert(‘test’, {'name': 'Civet', 'value': '18', 'num': '456.7'});
複製代碼
int id = await database.rawInsert('INSERT INTO test(name, value, num) VALUES("Civet", 18, 456.7)');
複製代碼
返回值須要注意下:Returns the number of changes made(返回受影響的的數量,即刪除的條目數量)。另外,若是沒where語句,會清空表格,要特別當心。
int count = await database.delete('test', where: 'name = ?', whereArgs: ['Civet']);
複製代碼
int count = await database.rawDelete('DELETE FROM test WHERE name = ?', ['Civet']);
複製代碼
返回值須要注意下:Returns the number of changes made(返回受影響的的數量).
int count = await database.update(
'test',
{'name': 'Home', 'value': '20'}
where: 'name = ?',
whereArgs: ['Civet']
);
複製代碼
int count = await database.rawUpdate('UPDATE test SET name = ?, value = ? WHERE name = ?', ['Home', 20, 'Civet']);
複製代碼
Query的返回值:List<Map<String, dynamic>>,List是行數據,Map是列數據。
Query是SQL語句中最複雜的一個,關鍵詞包含distinct, where, group by, having, count, order by asc/desc, limit, offset, in, join, as, nuion等等。
List<Map<String, dynamic>> result = await database.query(
‘test’,
distinct: true, // 是不是獨特的,便是否不讓重複
columns: ['name','value'], // 須要查詢的列
where: 'age > ?', // 查詢條件
whereArgs: [16], // 查詢條件參數
groupBy: 'name', // 按列分組
having: 'count(name) < 2', // 給分組設置條件
orderBy: 'name asc', // 按列排序 asc/desc
limit: 5, // 限制查詢結果數量
offset: 2, // 跳過幾條數據
);
複製代碼
List<Map<String, dynamic>> result = await database.rawQuery(
'SELECT distinct name, value FROM test WHERE age > ? group by name having count(name) < 2 order by name asc limit 5 offset 2',
[16],
);
複製代碼
對於 in 語句的組合:
/// SQL query where in
/// If colunm is INTEGER, use this method.
/// Reslut: (2, 3, 4)
static String whereInIntToString(List<int> data) {
String result;
for (int sub in data) {
if (result == null) {
result = '($sub';
} else {
result = '$result, $sub';
}
}
result = '$result)';
return result;
}
/// SQL query where in
/// If colunm is TEXT, use this method.
/// Reslut: ('2', '3', '4')
static String whereInStringToString(List<String> data) {
String result;
for (String sub in data) {
if (result == null) {
result = '(\'$sub\'';
} else {
result = '$result, \'$sub\'';
}
}
result = '$result)';
return result;
}
複製代碼
關於 join 的使用:
鏈接兩個表格的數據,使用on, using, natural限定鏈接條件,join分爲cross join(x * y的一種實現), inner join(默認join方式), outer join(只支持left outer join)。
用的比較多的是內鏈接inner join, 它把兩個表的數據,以限定條件on Table1.column = Table2.column組合起來。
/// SQL Statement
SELECT user_id, user_name, address, postal_code FROM at_user INNER JOIN at_address ON at_user.id = at_address.user_id;
複製代碼
須要注意的是,若是並不關心batch.commit()的返回值,傳入noResult爲true,這時候返回的List< dynamic >即爲null.
Batch batch = database.batch();
batch.insert('test', {'name': 'item'});
batch.update('test', {'name': 'new_item'}, where: 'name = ?', whereArgs: ['item']);
batch.delete('test', where: 'name = ?', whereArgs: ['item']);
List<dynamic> results = await batch.commit(
noResult: true, // 是否關心返回值
continueOnError: true, // 出現錯誤是否繼續
);
複製代碼
事務就像一個封閉的執行環境,只有當事務提交時,一系列操做對外才是可見的。
須要特別注意的是,事務執行期間,不能使用外部的DataBase對象,避免形成死鎖。
你能看出下面操做的返回值嗎?
/// 測試事務
Future<void> testTransaction({bool noResult, bool continueOnError}) async {
print('--------------- Transaction(noResult: $noResult, continueOnError: $continueOnError) --------------- ');
var db = await getDataBase();
// 清空表
await db.delete('test');
// 啓動事務
await db.transaction((Transaction txn) async {
// Ok
var batch = txn.batch();
// 操做(1-3):插入數據
batch.insert('test', {'name': 'item-0'});
batch.insert('test', {'name': 'item-1'});
batch.insert('test', {'name': 'item-2'});
// 操做4:更新一條數據
batch.update(
'test',
{'name': 'item-3'},
where: 'name = ?',
whereArgs: ['item-0'],
);
// 操做5:模擬異常: no such column no_column
batch.insert(
'test',
{'no_column': 'item-0'},
);
// 操做6:刪除一條數據
batch.delete(
'test',
where: 'name = ?',
whereArgs: ['item-3'],
);
// 執行上述一系列操做
List<dynamic> results = await batch.commit(
noResult: noResult,
continueOnError: continueOnError,
);
// 操做返回值
print('Reslut<dynamic>: $results');
});
// 事務執行後:查詢表中多少數據
List<Map<String, dynamic>> result = await db.rawQuery(
'SELECT * FROM Test',
);
print('List<Map> Reslut: $result');
print('------------------------------ ');
print('');
}
複製代碼
await testTransaction(noResult: false, continueOnError: false);
複製代碼
結果:
await testTransaction(noResult: false, continueOnError: true);
複製代碼
結果:
既然你已經讀到這裏了,返回值已經在你腦海中了。
有時候爲了調試,或許爲了分析數據,咱們須要查看數據庫中具體有哪些數據,經過可視化界面展現數據就比較方便了。
Android Studio的Plugin,直接在線(Marketplace)安裝便可。最上邊的狀態欄有個DB Navigator,而後點擊Database browser, 設置數據庫文件路徑。