Flutter 數據庫sqflite使用知識點

前言

sqflite是Flutter的SQLite插件,在App端可以高效的存儲和處理數據庫數據,官方地址:pub.flutter-io.cn/packages/sq…html

SQLite

關於SQLite的學習,推薦 菜 鳥 教 程 的 SQLite 教 程 sql

SQLite的特色:數據庫

  • 不須要單獨的服務器或操做系統
  • SQLite不須要配置,即不須要手動安裝和管理
  • 存儲在一個單一的跨平臺的磁盤文件
  • 不須要外部依賴,徹底自給自足
  • 輕量級

sqflite

sqflite全稱,個人理解是Structured Query Flutter Language Lite,即用於Flutter的輕量級結構化查詢語言。若是理解有誤,請及時更正,萬分謝謝!json

一塊兒看一下sqflite官方介紹:bash

用於Flutter的SQLite插件,支持iOS、Android、MacOS.服務器

  • 支持事務和批量操做
  • 程序打開期間,自動化版本管理
  • 增刪改查的幫助程序
  • 在iOS和Android後臺線程中執行DB操做

當前sqflite版本:1.2.0async

關於持久化存儲

關於App端的持久化存儲,用的比較多有shared_preferences、數據庫存儲、文件存儲。post

  • shared_preferences以key-value的方式存儲數據,是一種輕量級的數據持久化存儲方案。
  • 數據庫存儲數據量較大的場合,可以高效的存儲、組織和處理數據。
  • 像長篇文章、圖片、視頻等Size比較大的,利用File存儲。

sqflite支持的數據類型

若是你對英文比較自信,能夠嘗試讀這篇文章 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');
複製代碼

數據的增刪改查

Insert

返回值須要注意下: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)');
複製代碼

Delete

返回值須要注意下: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']);
複製代碼

Update

返回值須要注意下: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

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 批量操做

須要注意的是,若是並不關心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, // 出現錯誤是否繼續
);
複製代碼

Transaction 事務的支持

事務就像一個封閉的執行環境,只有當事務提交時,一系列操做對外才是可見的。

須要特別注意的是,事務執行期間,不能使用外部的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('');
}
複製代碼
  1. 須要返回值,錯誤不繼續執行
await testTransaction(noResult: false, continueOnError: false);
複製代碼

結果:

  • 發送異常 table Test has no column named no_column
  • 分析:操做5發生錯誤,不繼續執行,直接拋出異常。
  • 表中並未插入任何數據,操做所有Rollback
  1. 須要返回值,錯誤繼續執行
await testTransaction(noResult: false, continueOnError: true);
複製代碼

結果:

  • Reslut: [1, 2, 3, 1, DatabaseException(table Test has no column named no_column (Sqlite code 1): , while compiling: INSERT INTO Test (no_column) VALUES (?), (OS error - 2:No such file or directory)) sql 'INSERT INTO Test (no_column) VALUES (?)' args [item-0]}, 1]
  • List Reslut: [{id: 2, name: item-1, value: null, num: null}, {id: 3, name: item-2, value: null, num: null}]
  • 分析:操做5發生錯誤,繼續執行,操做5返回值是一個異常,其餘操做正常
  1. 不須要返回值,錯誤不繼續執行
  2. 不須要返回值,錯誤繼續執行

既然你已經讀到這裏了,返回值已經在你腦海中了。

可視化界面

有時候爲了調試,或許爲了分析數據,咱們須要查看數據庫中具體有哪些數據,經過可視化界面展現數據就比較方便了。

1. Database Navigator

Android Studio的Plugin,直接在線(Marketplace)安裝便可。最上邊的狀態欄有個DB Navigator,而後點擊Database browser, 設置數據庫文件路徑。

2. SQLiteStudio

相關文章
相關標籤/搜索