Flutter第十三章(sqflite 數據庫,數據庫的 CRUD操作,數據庫的事務和批處理)

版權聲明:本文為作者原創書籍。轉載請注明作者和出處,未經授權,嚴禁私自轉載,侵權必究!!!

情感語錄: 生活本來就是一場惡戰,給止疼藥也好,給巴掌也罷,最終都是要單槍匹馬練就自身膽量,誰也不例外。

歡迎來到本章節,上一章節介紹了用shared_preferences和文件操作實現持久化,知識點回顧 戳這里 Flutter基礎第十二章

承繼上一篇文章的伏筆,本章知識點主要介紹 Flutter 的數據持久化之數據庫。很多時候我們的數據并不是單一結構且存在關系性,并需對大批量數據有增、刪、改、查 操作時那么對數據庫的使用那就是必不可少了。

本章簡要:

1、sqflite 數據庫

2、數據庫的 CRUD操作

3、數據庫的事務和批處理

一、sqflite 數據庫

sqflite數據庫是一款輕量級的關系型數據庫,如同 iOS和Android中的SQLite。sqflite地址:https://github.com/tekartik/sqflite

sqflite 插件引入

1、在pubspec.yaml文件中添加依賴
    dependencies:
      fluttertoast: ^3.0.3
      flutter:
        sdk: flutter
      #添加持久化插件 sp
      shared_preferences: ^0.5.3+1
      #添加文件庫
      path_provider: ^1.2.0
      #添加數據庫
      sqflite: ^1.1.6

本人使用的當前最新版本 1.1.6,讀者想體驗最新版本請在使用時參看最新版本號進行替換。

2、安裝依賴庫

執行 flutter packages get 命令;AS 開發工具直接右上角 packages get也可。

3、在需要使用的地方導包引入
import 'package:sqflite/sqflite.dart';

sqflite 支持的數據類型

    存儲類             描述
     
    NULL              值是一個 NULL 值。
    
    INTEGER           值是一個帶符號的整數,-2^63 到 2^63 - 1
    
    REAL              值是一個數字類型,dart中的 num
    
    TEXT              值是一個文本字符串,dart中的 String
    
    BLOB              值是一個 blob 數據,dart中的 Uint8List 或者 List<int> 

可以看出 sqflite 中支持的數據類型比較少,比如 bool 、DateTime都是不支持的;開發中需要 bool 類型可以使用 INTEGER的 0和1來表示,DateTime 類型可以使用 時間戳 字符串。

sqflite 中常用的 API:

getDatabasesPath() : 獲取數據庫位置,在Android上,它通常是data/data/包名/databases;在iOS上,它是Documents目錄。

join("參數1", "參數2"): 該方法表示創建數據庫, 參數1: getDatabasesPath() 獲取到的數據庫存放路徑,參數2: 數據庫的名字,如:User.db

openDatabase(): 該方法表示打開數據,具體有以下幾個重要參數

Future<Database> openDatabase(
    String path,  
    {int version,
    OnDatabaseConfigureFn onConfigure, 
    OnDatabaseCreateFn onCreate,
    OnDatabaseVersionChangeFn onUpgrade,
    OnDatabaseVersionChangeFn onDowngrade,
    OnDatabaseOpenFn onOpen,
    bool readOnly = false,
    bool singleInstance = true})

path:必傳參數,join() 創建數據庫后的返回值。

version: 當前的版本號。

onConfigure: 數據庫的相關配置

onCreate: 創建表的方法

onUpgrade、onDowngrade: 數據庫版本的升降級

readOnly: 是否為只讀方式打開。

CRUD 相關API:

1、插入數據的兩種方式:
Future<int> insert(String table, Map<String, dynamic> values,
      {String nullColumnHack, ConflictAlgorithm conflictAlgorithm});

Future<int> rawInsert(String sql, [List<dynamic> arguments]);

insert() 方法第一個參數為操作的表名,第二個參數 map 中是想要添加的字段名和對應字段值。

rawInsert() 方法第一個參數為一條插入sql 語句,語句中?作為占位符,通過第二個參數填充占位數據。

2、查詢數據的兩種方式:
Future<List<Map<String, dynamic>>> query(String table,
      {bool distinct,
      List<String> columns,
      String where,
      List<dynamic> whereArgs,
      String groupBy,
      String having,
      String orderBy,
      int limit,
      int offset});
      
Future<List<Map<String, dynamic>>> rawQuery(String sql,
      [List<dynamic> arguments]);
2.1 query() 方式查詢的參數介紹:
    參數           描述

    table          表名

    distinct       是否去重

    columns        查詢字段集合

    where          WHERE子句(使用?作為占位符)

    whereArgs      WHERE子句占位符參數值

    groupBy        結果集分組

    having         結合groupBy使用過濾結果集

    orderBy        排序方式

    limit          查詢的條數

    offset         查詢的偏移位
2.2 rawQuery() 方法第一個參數為一條查詢sql語句,使用 ?作為占位符,通過第二個參數填充數據。
3. 修改數據的兩種方式
Future<int> update(String table, Map<String, dynamic> values,
      {String where,
      List<dynamic> whereArgs,
      ConflictAlgorithm conflictAlgorithm});
      
Future<int> rawUpdate(String sql, [List<dynamic> arguments]);

update()方法第一個參數為操作的表名,第二個參數為修改的字段和對應值,后邊的可選參數依次表示WHERE子句、WHERE子句占位符參數值、發生沖突時的操作算法(包括回滾、終止、忽略等)。

rawUpdate() 方法第一個參數為一條更新sql語句,使用?作為占位符,通過第二個參數填充數據。

4. 刪除數據的兩種方式
Future<int> delete(String table, {String where, List<dynamic> whereArgs});

Future<int> rawDelete(String sql, [List<dynamic> arguments]);

delete() 方法第一個參數為操作的表名,后邊的可選參數依次表示WHERE子句、WHERE子句占位符參數值。

rawDelete() 方法第一個參數為一條刪除sql語句,使用?作為占位符,通過第二個參數填充數據。

close() 關閉數據庫。

transaction() 開啟事務。

batch() 獲取批處理對象。

可以看到 Flutter 在 增、刪、改、查 中都提供了兩套方法使用,更傾向于寫 sql 語句的客官們使用 rawxxx 方式就比較好;但我還是喜歡通過參數拼接組合的方式,可以屏蔽很多細節問題。

二、數據庫的 CRUD

上面對 sqflite 引入和相關 API 都做了介紹,如果還是不清楚怎么使用,接下來就通過 案例的方式去學習。為了不重復的敘述,這里需要提前先做兩個準備工作:① 新建一個用戶實體類(方便數據操作)。 ② 建庫、建表。

用戶實體類

    class UserInfo{

      String name;

      String password;

      int age;


      UserInfo({this.name,this.age,this.password});


      UserInfo.toUser(Map<String, dynamic> json) {
        name = json['name'];
        age = json['age'];
        password = json['password'];
      }


      Map<String, dynamic> toMap() {
        Map<String, dynamic> data = new Map<String, dynamic>();
        data['name'] = this.name;
        data['age'] = this.age;
        data['password'] = this.password;
        return data;
      }

    }

建庫、建表

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

    import 'UserInfo.dart';

    class SqlUserHelper{

      //數據庫
      final String dataBaseName = "User.db";

      //數據表
      final String tableName = "USER_TABLE";

      //以下是表中的列名
      final String columnId = 'id';
      final String name = 'name';
      final String password = 'password';
      final String age ="age";

      // 靜態私有成員
      static SqlUserHelper _instance;

      Database _database;
      // 私有構造函數
      SqlUserHelper._() {

        initDb();
      }

      //私有訪問點
      static SqlUserHelper helperInstance() {
        if (_instance == null) {
          _instance = SqlUserHelper._();
        }
        return _instance;
      }


      //初始化數據庫
       void initDb() async {

        String databasesPath = await getDatabasesPath();
        String path = join(databasesPath, dataBaseName );

        // openDatabase 指定是數據庫路徑,版本號,和執行表的創建
        _database = await openDatabase(path, version: 1, onCreate: _onCreate);
      }

      //創建UserInfo表
      void _onCreate(Database db, int newVersion) async {

        await db.execute('CREATE TABLE $tableName($columnId INTEGER PRIMARY KEY AUTOINCREMENT, $name TEXT, $password TEXT, $age INTEGER)');
      }

      ///關閉數據庫
      Future<void> close() async {
        return _database.close();
      }

    }

UserInfo 實體比較簡單只聲明了三個屬性而已,另外兩個方法只是為了插入數據和查詢時方便觀看而已。SqlUserHelper 是一個單例模式的用戶數據庫操作類( 單例模式不清楚的請看上一章節)。在使用完數據庫時一定要及時關閉數據庫,避免造成不必要的資源浪費。不持續叨逼叨.... 代碼中已有詳細注釋。

1、添加數據

首先向數據庫插入幾條數據,方便后面查詢使用。在 SqlUserHelper 中添加插入數據的方法。

插入數據的兩種方式
      /// insert第一種
      Future<int> insert(UserInfo userInfo){
        return _database.insert(tableName, userInfo.toMap());
      }

      /// insert第二種
      Future<int> rawInsert(UserInfo userInfo){
        return _database.rawInsert("INSERT INTO $tableName ($name,$password,$age) VALUES(?, ?, ?)", [userInfo.name,userInfo.password,userInfo.age]);
      }

實例代碼:

    //構建一個user 對象
     UserInfo user = UserInfo(name: userName,password: userPass,age: age);

    //向數據庫插入該條數據
     sqlUserHelper.insert(user).then((value){
      print("the last insert id $value");
    });
    
    //構建一個user 對象
    UserInfo user = UserInfo(name: userName,password: userPass,age: age);

    //向數據庫插入該條數據
    sqlUserHelper.rawInsert(user).then((value){
      print("the last rawInsert id $value");
    });

無論是通過 insert 還是 rawInsert方式插入數據,只要成功插入就會返回最后一條插入的記錄 ID 回來。

實操演示:

insert.gif

控制臺輸出:

    I/flutter: the last insert id 1
    I/flutter: the last rawInsert id 2

控制臺打印出了通過兩種方式插入數據后的記錄 ID,證明此時已經成功插入了兩條一樣的數據到數據庫。

2、查詢數據
查詢數據的兩種方式
      ///第一種 query
      Future<List<Map>> query() async {
        List<Map> maps = await _database.query(tableName);
        if (maps.isNotEmpty) {
          return maps;
        }
        return null;
      }

      ///第二種 query
      Future<List<Map>> rawQuery() async {
        List<Map> maps = await _database.rawQuery("SELECT * FROM $tableName");
        if (maps.isNotEmpty) {
          return maps;
        }
        return null;
      }

實例代碼:

  ///查詢全部
   sqlUserHelper.query().then((value){
    print("the query info  ${value.toString()}");
  });
  
  
   ///查詢全部
  sqlUserHelper.rawQuery().then((value){
    print("the rawQuery info  ${value.toString()}");
  });

兩種方式都是查詢整個 user表中的全部信息,并將結果打印輸出。下面來查詢上面添加的數據。

實操演示:

query.gif

可以看到在我添加數據后,分別點了 query 和 rawQuery 方式查詢,控制臺輸出結果如下:

I/flutter: the query info  [{id: 1, name: zhengzaihong, password: 123456, age: 18}, {id: 2, name: zhengzaihong, password: 123456, age: 18}]

I/flutter: the rawQuery info  [{id: 1, name: zhengzaihong, password: 123456, age: 18}, {id: 2, name: zhengzaihong, password: 123456, age: 18}]

兩種方式都查詢出了 全部的信息,實際開發中一般都是條件查詢,這里只是簡單除暴的演示而已。

3、修改數據

上面有了兩條一樣的數據,接下來方便我們做修改,然后再次查詢輸出結果看是否正確修改。

修改數據的兩種方式
      ///第一種 update
      Future<int> update(UserInfo user,int id) async {
        return await _database.update(tableName,user.toMap(),where: '$columnId = ?', whereArgs: [id]);
      }

      ///第二種 rawUpdate
      Future<int> rawUpdate(UserInfo user,int id) async {
        return await _database.rawUpdate("UPDATE $tableName SET  $name = ?  WHERE $columnId = ? ",[user.name,id]);
      }

實例代碼:

      //構建一個user 對象 根據 ID 修改
      UserInfo user = UserInfo(name: userName,password: userPass,age: age);
      sqlUserHelper.update(user, 1).then((value){
        print("the update info  ${value.toString()}");
      });
      
      UserInfo user = UserInfo(name: userName,password: userPass,age: age);
      sqlUserHelper.rawUpdate(user, 2).then((value){
        print("the rawUpdate info  ${value.toString()}");
      });

實操演示

update.gif

控制臺輸出:

I/flutter: the update info  1

I/flutter: the rawUpdate info  1

I/flutter: the query info  [{id: 1, name: zzh, password: 123456, age: 20}, {id: 2, name: LQ, password: 123456, age: 18}]

I/flutter: the rawQuery info  [{id: 1, name: zzh, password: 123456, age: 20}, {id: 2, name: LQ, password: 123456, age: 18}]

從輸出可以看出 無論是 update 還是 rawUpdate方式修改數據都打印出有一行受影響,這表示數據被成功修改了。update 方式把輸入框中的數據重新帶入把 id =1 的這條數據全部重新賦值一遍,實現了數據修改;而 rawUpdate 方式只在 sql 語句中加入了對名字的修改,可以看到在輸入框填寫了年齡值也并未修改成功。

4、刪除數據
刪除數據的兩種方式
      ///第一種 delete 根據id刪除
      Future<int> delete(int id) async {
        return await _database.delete(tableName,
            where: "$columnId = ?", whereArgs: [id]);
      }


      ///第二種 delete  根據id刪除
      Future<int> rawDelete(int id) async {
        return await _database.rawDelete("DELETE FROM $tableName WHERE $columnId = ?", [id]);
      }

實例代碼:

  /// 根據 id 刪除
  sqlUserHelper.delete(1).then((value){
    print("the delete info  ${value.toString()}");
  });
  
  /// 根據 id 刪除
  sqlUserHelper.rawDelete( 2).then((value){
    print("the rawDelete info  ${value.toString()}");
  });

實操演示:

delete.gif

在我點擊兩次刪除按鈕后,再次查詢結果如下:

    I/flutter: the delete info  1

    I/flutter: the rawDelete info  1

    I/flutter: the query info  null

查詢結果輸出為 null 表示數據庫中已經沒有開始存入的兩條數據了,證明兩次刪除都是成功的。

三、數據庫的事務和批處理

1、事務

sqflite同時支持事務,所謂事務,它是一個操作序列,這些操作要么都執行,要么都不執行,即:執行單個邏輯功能的一組指令或操作稱為事務。

下面通過事務來添加兩條數據:

  /// 開啟事務添加
  Future<bool> transactionInsert(UserInfo userInfo1, UserInfo userInfo2) async {
    return await _database.transaction((Transaction transaction) async {

      int id1 = await transaction.insert(tableName, userInfo1.toMap());

      int id2 = await transaction.insert(tableName, userInfo2.toMap());

      return id1 != null && id2 != null;
    });
  }

實例代碼:

  //構建兩個用戶對象
  
  UserInfo user1 = UserInfo(name: "zhengxian",password: "123456",age: 18);
  
  UserInfo user2 = UserInfo(name: "zzh",password: "123456",age: 20);
  
  sqlUserHelper.transactionInsert(user1, user2).then((value){
      print("transaction result: $value");
  });

這里就不貼圖了,下面直接來看通過事務來添加的兩條數據和查詢結果,控制臺輸出:

I/flutter: transaction result: true
I/flutter: the query info  [{id: 1, name: zhengxian, password: 123456, age: 18}, {id: 2, name: zzh, password: 123456, age: 20}]

結果返回 ture ,查詢結果也是上面構建的兩條數據信息,說明都是成功的。

2、批處理

sqflite支持批處理操作,批處理指的是一次操作中執行多條SQL語句,批處理相比于一次一次執行效率會提高很多。

下面通過批處理來新增一條數據和修改一條數據

    /// 批處理
    Future<List<dynamic>> batch(UserInfo user,UserInfo user2) async {

     Batch batch =  _database.batch();
     //先添加一條數據
     batch.insert(tableName, user.toMap());
     //修改 id 為1的值
     batch.update(tableName,user2.toMap(),where: '$columnId = ?', whereArgs: [1]);

     return batch.commit();
    }

實例代碼:

  UserInfo user1 = UserInfo(name: "zhengxian",password: "123456",age: 18);

  UserInfo user2 = UserInfo(name: "LiShi",password: "123456",age: 55);

  sqlUserHelper.batch( user1,user2).then((value){
    print("the batch info  ${value.toString()}");
  });

控制臺打印批處理和查詢結果:

    I/flutter: the batch info  [3, 1]
    I/flutter: the query info  [{id: 1, name: LiShi, password: 123456, age: 55}, {id: 2, name: zzh, password: 123456, age: 20}, 
          {id: 3, name: zhengxian, password: 123456, age: 18}]

[3, 1] 兩個數字分別表示最后一次插入數據后的記錄 id,和有一行修改受影響。通過查詢我可以看出,數據確實成功插入到了數據庫,且 id=1 的這條數據被成功修改了。

下面貼出下數據庫操作的源碼,界面源碼就不貼了,代碼全部詳情會在文末給出:

  import 'package:sqflite/sqflite.dart';
  import 'package:path/path.dart';
  
  import 'UserInfo.dart';
  
  class SqlUserHelper{
  
    //數據庫
    final String dataBaseName = "User.db";
  
    //數據表
    final String tableName = "USER_TABLE";
  
    //以下是表中的列名
    final String columnId = 'id';
    final String name = 'name';
    final String password = 'password';
    final String age ="age";
  
    // 靜態私有成員
    static SqlUserHelper _instance;
  
    Database _database;
    // 私有構造函數
    SqlUserHelper._() {
  
      initDb();
    }
  
    //私有訪問點
    static SqlUserHelper helperInstance() {
      if (_instance == null) {
        _instance = SqlUserHelper._();
      }
      return _instance;
    }
  
  
    //初始化數據庫
     void initDb() async {
  
      String databasesPath = await getDatabasesPath();
      String path = join(databasesPath, dataBaseName );
  
      // openDatabase 指定是數據庫路徑,版本號,和執行表的創建
      _database = await openDatabase(path, version: 1, onCreate: _onCreate);
    }
  
    //創建UserInfo表
    void _onCreate(Database db, int newVersion) async {
  
      await db.execute('CREATE TABLE $tableName($columnId INTEGER PRIMARY KEY AUTOINCREMENT, $name TEXT, $password TEXT, $age INTEGER)');
    }
  
  
    /// 插入數據的兩種方式
    ///
    /// insert第一種
    Future<int> insert(UserInfo userInfo){
      print("插入數據:${ userInfo.toMap()}");
      return _database.insert(tableName, userInfo.toMap());
    }
  
    /// insert第二種
    Future<int> rawInsert(UserInfo userInfo){
      return _database.rawInsert("INSERT INTO $tableName ($name,$password,$age) VALUES(?, ?, ?)", [userInfo.name,userInfo.password,userInfo.age]);
    }
  
  
    ///查詢數據的兩種方式
  
    ///第一種 query
    Future<List<Map>> query() async {
      List<Map> maps = await _database.query(tableName);
      if (maps.isNotEmpty) {
        return maps;
      }
      return null;
    }
  
    ///第二種 query
    Future<List<Map>> rawQuery() async {
      List<Map> maps = await _database.rawQuery("SELECT * FROM $tableName");
      if (maps.isNotEmpty) {
        return maps;
      }
      return null;
    }
  
  
  
    ///修改數據的兩種方式
  
    ///第一種 update
    Future<int> update(UserInfo user,int id) async {
      return await _database.update(tableName,user.toMap(),where: '$columnId = ?', whereArgs: [id]);
    }
  
    ///第二種 rawUpdate
    Future<int> rawUpdate(UserInfo user,int id) async {
      return await _database.rawUpdate("UPDATE $tableName SET  $name = ?  WHERE $columnId = ? ",[user.name,id]);
    }
  
  
    ///刪除數據的兩種方式
    ///
    ///第一種 delete 根據id刪除
    Future<int> delete(int id) async {
      return await _database.delete(tableName,
          where: "$columnId = ?", whereArgs: [id]);
    }
  
  
    ///第二種 delete  根據id刪除
    Future<int> rawDelete(int id) async {
      return await _database.rawDelete("DELETE FROM $tableName WHERE $columnId = ?", [id]);
    }
  
  
    /// 開啟事務添加
    Future<bool> transactionInsert(UserInfo userInfo1, UserInfo userInfo2) async {
      return await _database.transaction((Transaction transaction) async {
  
        int id1 = await transaction.insert(tableName, userInfo1.toMap());
  
        int id2 = await transaction.insert(tableName, userInfo2.toMap());
  
        return id1 != null && id2 != null;
      });
    }
  
  
     /// 批處理
     Future<List<dynamic>> batch(UserInfo user,UserInfo user2) async {
  
       Batch batch =  _database.batch();
       //先添加一條數據
       batch.insert(tableName, user.toMap());
       //修改 id 為1的值
       batch.update(tableName,user2.toMap(),where: '$columnId = ?', whereArgs: [1]);
  
       return batch.commit();
    }
  
  
    ///關閉數據庫
    Future<void> close() async {
      return _database.close();
    }
  
  }

至此數據庫的一些用法也就介紹完了,加上前一篇文章中介紹的 ShardPreferences 和 IO 文件讀寫方式總共介紹了三種方式可以實現數據持久化。ShardPreferences多用于一些簡單無關系性的數據存儲;IO 文件的讀寫其實在開發中很少使用來存儲數據,往往都是做一些文檔文件才會使用。

好了本章節到此結束,又到了說再見的時候了,如果你喜歡請留下你的小紅星,創作真心也不容易;你們的支持才是創作的動力,如有錯誤,請熱心的你留言指正, 謝謝大家觀看,下章再會 O(∩_∩)O

實例源碼地址:https://github.com/zhengzaihong/flutter_learn/tree/master/lib/page/database

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容