美文网首页FlutterFlutter
Flutter第十三章(sqflite 数据库,数据库的 CRU

Flutter第十三章(sqflite 数据库,数据库的 CRU

作者: 一巴掌拍出两坨脂肪 | 来源:发表于2021-05-03 16:01 被阅读0次
    版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!

    情感语录: 生活本来就是一场恶战,给止疼药也好,给巴掌也罢,最终都是要单枪匹马练就自身胆量,谁也不例外。

    欢迎来到本章节,上一章节介绍了用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

    相关文章

      网友评论

        本文标题:Flutter第十三章(sqflite 数据库,数据库的 CRU

        本文链接:https://www.haomeiwen.com/subject/xszhyctx.html