二次封装Sqlite基类(Flutter 教程) - 汇站网

二次封装Sqlite基类(Flutter 教程)

2023-11-23 0 1,016

正文:

Flutter中,我们可以使用 Sqlite 作为本地数据库来存储和管理数据。为了方便使用,我们可以进行二次封装,创建一个基类来处理常见的数据库操作。
二次封装Sqlite基类(Flutter 教程)

安装 Sqlite 插件

首先我们需要安装 Sqlite 插件

// https://www.huizhanii.com
sqflite: ^2.0.2

创建基类,用来实例化数据库

// https://www.huizhanii.com
import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {
	static const String _dbName = "xxx";//数据库名称
  static const int _newVersion = 1;//数据库版本
  static int _oldVersion = 0;//数据库上一个版本
  static String? _dbBasePath;//数据库地址
  static Database? _database;//数据库实例

  EntityPlus() {
  	_initDatabase();
  }

  ///初始化数据库
  Future<Database> _initDatabase() async {
    //获取数据库的位置
    _dbBasePath ??= await getDatabasesPath() + "/$_dbName.db";

    //打开数据库
    _database ??= await openDatabase(
      _dbBasePath!,
      version: _newVersion,
      // onConfigure: (db) { },//数据库初始化时触发的回调
      // onOpen: (db) { },//数据库被打开时触发的回调
      // onCreate: (db, version){},//创建数据库时触发的回调
      onUpgrade: (db, oldVersion, newVersion){//数据库升级时触发的回调
        /*
        	这里需要注意, 在后面时会用到 _oldVersion, _oldVersion 的变化会触发子类的某些方法
        */
        _oldVersion = old;
      },
      onDowngrade: (db, oldVersion, newVersion){//数据库降级时触发的回调
        /*
        	这里需要注意, 在后面时会用到 _oldVersion, _oldVersion 的变化会触发子类的某些方法
        */
        _oldVersion = old;
      },
    );

    return _database!;
  }
}

基类已经准备好初始化数据库的工作。当子类继承基类时,会触发数据库初始化事件。在数据库初始化完成后,基类还需要执行以下操作:

1. 创建表格:触发子类的创建表格事件。但是,如果表格已经存在,重复创建会导致错误,因此该函数只能触发一次。

2. 数据库升级或降级:触发子类的数据库升级或降级事件,并且也只能触发一次。

添加建表功能

为了触发子类的创建表格事件,并且只触发一次,我们可以在基类中定义一个创建表格的函数,并要求子类必须重写该函数。在重写函数中,判断表格是否存在,如果不存在则创建。

// https://www.huizhanii.com
import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {
	//...代码省略
  
  abstract String tableName;//数据表名称,在子类中必须要重写的字段
  bool exists = false;//数据表是否存在

  ///建表函数, 在子类中必须重写
	Future<void> onCreate(Database db, int version);

  EntityPlus() {
  	_initDatabase();
  }

  ///初始化数据库
  Future<Database> _initDatabase() async {
    //...省略获取数据库的位置代码
    //...省略打开数据库代码

    //判断表是否存在
    exists = await tableExists();
    if(!exists){
      //表不存在时调用建表函数
      await onCreate(_database!, _newVersion);
      exists = true;
    }

    return _database!;
  }

  ///判断表是否存在
  Future<bool> tableExists() async {
    //内建表sqlite_master
    var res = await _database!.rawQuery(
      "SELECT * FROM sqlite_master WHERE TYPE = 'table' AND NAME = '$tableName'",
    );
    return res.isNotEmpty;
  }
}

数据库升级或降级

在基类中我们实现了建表的功能, 同理数据库升级或降级也可以这样写

// https://www.huizhanii.com
import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {
	//...代码省略
  
  abstract String tableName;//数据表名称,在子类中必须要重写的字段
  bool exists = false;//数据表是否存在

  ///建表函数, 在子类中必须重写
	Future<void> onCreate(Database db, int version);

  ///数据库升级时触发的函数,子类中可以根据需要时进行重写
  onUpgrade(Database db, int oldVersion, int newVersion) {}

  ///数据库降级触发的函数,子类中可以根据需要时进行重写
  onDowngrade(Database db, int oldVersion, int newVersion) {}

  EntityPlus() {
  	_initDatabase();
  }

  ///初始化数据库
  Future<Database> _initDatabase() async {
    //...省略获取数据库的位置代码
    //...省略打开数据库代码
    //...省略建表代码

    //数据第一次创建时 _oldVersion 等于 0, 所以忽略
    if (_oldVersion != 0) {
      if (_oldVersion > _newVersion) {							//判断是否降级了
        print("_oldVersion === $_oldVersion");
        print("_newVersion === $_newVersion");
        //数据库降级了,如果子类重写了 onDowngrade 方法, 则调用的是子类的;
        await onDowngrade(
          _database!,
          await _database!.getVersion(),
          _newVersion,
        );
      } else if (_oldVersion < _newVersion) {			//判断是否升级了
        print("_oldVersion === $_oldVersion");
        print("_newVersion === $_newVersion");
        //数据库升级了,如果子类重写了 onUpgrade 方法, 则调用的是子类的;
        await onUpgrade(
          _database!,
          await _database!.getVersion(),
          _newVersion,
        );
      }
    }

    return _database!;
  }
}

简易版增删改查

好了现在我们有了建表的功能, 但是我们还需要对表进行增删改查, 所以接下来我们封装一个简易的增删改查功能

// https://www.huizhanii.com
import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {
	//...代码省略

  Database get database {
    return _database!;
  }

  ///插入数据
  insert(Map<String, Object?> values) async {
    return database.insert(tableName, values);
  }

  ///删除数据
  remove(Map<String, Object?> json) async {
    var database = await getDatabase();

    List<String> keys = json.keys.toList();
    List<String> where = [];
    for (int i = 0; i < keys.length; i++) {
      String key = keys[i];
      where.add("$key=${json[key]}");
    }

    return database.delete(
      tableName,
      where: where.join(" and "),
    );
  }

  ///修改数据
  update(Map<String, Object?> json1, Map<String, Object?> json2) async {
    List<String> keys = json1.keys.toList();
    List<String> where = [];
    for (int i = 0; i < keys.length; i++) {
      String key = keys[i];
      if (json1[key].runtimeType == String) {
        where.add("$key='${json1[key]}'");
      } else {
        where.add("$key=${json1[key]}");
      }
    }

    return database.update(
      tableName,
      json2,
      where: where.isEmpty ? null : where.join(" and "),
    );
  }

  ///缓存的数据
  static final Map<String, List<Map<String, Object?>>> _findCache = {};

  ///查找数据
  Future<List<Map<String, Object?>>> find({
    Map<String, dynamic>? where,
    int? page,
    int? pageSize,
  }) async {
    List<String> keys = where?.keys.toList() ?? [];

    List<String> whereList = [];
    for (int i = 0; i < keys.length; i++) {
      String key = keys[i];
      if (where![key].runtimeType == String) {
        whereList.add("$key='${where[key]}'");
      } else {
        whereList.add("$key=${where[key]}");
      }
    }

    String sql = whereList.join(" and ");
    String mapKey = "${tableName}_${sql}_page=${page}_pageSize=$pageSize";

    List data = sql.isEmpty ? [] : (_findCache[mapKey] ?? []);
    if (data.isNotEmpty) {
      return _findCache[mapKey]!;
    }

    var result = await database.query(
      tableName,
      where: sql.isEmpty ? null : sql,
      offset: page == null ? null : (page - 1) * (pageSize ?? 1),
      limit: pageSize,
    );
    if (sql.isNotEmpty) {
      _findCache[mapKey] = result;
    }
    return result;
  }

  rawQuery(String sql) async {
    return database.rawQuery(sql);
  }
}

开始实验

新建一个 user_info 实体类, 继承 EntityPlus

// https://www.huizhanii.com
class UserInfoEntity extends EntityPlus {
  @override
  String tableName = "user_info";

  //建表函数,当数据库中没有这个表时,基类会触发这个函数
  @override
  onCreate(db, version) async {
    print("创建 $tableName 数据表");
    await db.execute("""
      CREATE TABLE $tableName (
        id integer primary key autoincrement,
        name TEXT,
        sex INTEGER,
        phone TEXT
      )
    """);
  }

  ///当数据库升级时,基类会触发的函数
  @override
  onUpgrade(db, oldVersion, newVersion) {}

  ///当数据库降级,基类会触发的函数
  @override
  onDowngrade(db, oldVersion, newVersion) {}
}

页面中操作 user_info 表

// https://www.huizhanii.com
class Test extends StatefulWidget {
  const Test({Key? key}) : super(key: key);

  @override
  State<Test> createState() => _TestState();
}

class _TestState extends State<Test> {
  @override
  void initState() {
    super.initState();

    try {
      UserInfoEntity userInfoEntity = UserInfoEntity();
      userInfoEntity.find();
    } catch (err) {
      //注意由于打开数据库属于异步操作, 虽然 UserInfoEntity 已经实例化了, 但是数据库的初始化并没有完成
      print("报错了: $err");
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return const Scaffold();
  }
}

在这种情况下,我们需要考虑一些问题:

1. 当我们直接实例化时,打开数据库是一个异步操作,数据库可能还没有初始化完成,因此操作数据库肯定会发生异常。
2. 每次操作数据库都要实例化一次,这可能会浪费内存。

为了解决以上问题,我们可以将 UserInfoEntity 改成单例模式。

// https://www.huizhanii.com
class UserInfoEntity extends EntityPlus {
  static UserInfoEntity? _ins;

  UserInfoEntity._();

  static UserInfoEntity instan() {
    if (_ins == null) {
      print("实例化");
    }
    return _ins ??= UserInfoEntity._();
  }
  
  @override
  String tableName = "user_info";

  //建表函数,当数据库中没有这个表时,基类会触发这个函数
  @override
  onCreate(db, version) async {
    print("创建 $tableName 数据表");
    await db.execute("""
      CREATE TABLE $tableName (
        id integer primary key autoincrement,
        name TEXT,
        sex INTEGER,
        phone TEXT
      )
    """);
  }

  ///当数据库升级时,基类会触发的函数
  @override
  onUpgrade(db, oldVersion, newVersion) {}

  ///当数据库降级,基类会触发的函数
  @override
  onDowngrade(db, oldVersion, newVersion) {}
}

然后再新建一个类,用来存放所有实体类, 将所有实体类先进行实例化

// https://www.huizhanii.com
import 'entitys/user_info.entity.dart';

class MySqlite {
  static forFeature() async {
    var list = [
      UserInfoEntity.instan(),
      //...其他的表实体类
    ];
    for (int i = 0; i < list.length; i++) {
      var entity = list[i];

      //是否还记得基类中定义的 exists 字段,这是用来判断表是否创建完成
      while (!entity.exists) {
        //等待数据表创建完成
        await Future.delayed(const Duration(milliseconds: 60), () {});
      }
    }
  }
}

由于第一次安装应用,设备上并不存在我们需要的数据表,所以需要等待数据表的创建完成,以防止在进入页面时直接对表进行操作时发生异常。

在 main 函数中执行 MySqlite.forFeature(),等待数据库初始化完成并且所有的表都已经创建完成后,再执行下面的代码:

// https://www.huizhanii.com
void main() async {
  await MySqlite.forFeature();
  runApp(const MyApp());
}

需要注意的是,等待数据库和表的初始化完成需要一定的时间,这种方式会阻塞页面。如果表的数量很多,白屏时间会加长。可以将这个方法放在启动页中,等初始化完毕后再进入主页面。由于我的程序数据表并不多,所以我直接将它写在 main 函数中了。

此时,在页面中可以直接对数据库进行增删改查的操作了。

// https://www.huizhanii.com
UserInfoEntity userInfoEntity = UserInfoEntity.instan();

userInfoEntity.insert({
  "name": "汇站",
  "sex": 1,
  "phone": "123456789",
});
userInfoEntity.find();
userInfoEntity.update();
userInfoEntity.remove({"id": 1});

基类的完整代码

// https://www.huizhanii.com
import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {
  static const String _dbName = "zjdk_plus";
  static const int _newVersion = 1;
  static int _oldVersion = 0;
  static String? _dbBasePath;

  static Database? _database;

  ///表名称
  abstract String tableName;

  ///表是否存在
  bool exists = false;

  ///数据库实例化完成
  onReload(Database db, int version) {}

  ///创建表
  Future<void> onCreate(Database db, int version);

  ///更新表
  onUpgrade(Database db, int oldVersion, int newVersion) {}

  ///数据库降级
  onDowngrade(Database db, int oldVersion, int newVersion) {}

  EntityPlus() {
    _initDatabase();
  }

  ///创建数据库
  Future<Database> _initDatabase() async {
    _dbBasePath ??= await getDatabasesPath() + "/$_dbName.db";
    _database ??= await openDatabase(
      _dbBasePath!,
      version: _newVersion,
      // onConfigure: (db) { },
      // onCreate: onCreate,
      onUpgrade: (db, old, newV) {
        _oldVersion = old;
      },
      onDowngrade: (db, old, newV) {
        _oldVersion = old;
      },
      // onOpen: onOpen,
    );

    onReload(_database!, _newVersion);

    //判断表是否存在
    exists = await tableExists();
    if (!exists) {
      await onCreate(_database!, _newVersion);
      exists = true;
    }

    if (_oldVersion != 0) {
      if (_oldVersion > _newVersion) {
        print("_oldVersion === $_oldVersion");
        print("_newVersion === $_newVersion");
        //数据库降级了
        await onDowngrade(
          _database!,
          await _database!.getVersion(),
          _newVersion,
        );
      } else if (_oldVersion < _newVersion) {
        print("_oldVersion === $_oldVersion");
        print("_newVersion === $_newVersion");
        //数据库升级了
        await onUpgrade(
          _database!,
          await _database!.getVersion(),
          _newVersion,
        );
      }
    }

    return _database!;
  }

  ///表是否存在
  Future<bool> tableExists() async {
    //内建表 sqlite_master
    var res = await _database!.rawQuery(
      "SELECT * FROM sqlite_master WHERE TYPE = 'table' AND NAME = '$tableName'",
    );
    return res.isNotEmpty;
  }

  ///表列是否存在
  Future<bool> columnExists(String columnName) async {
    var result = await _database!.rawQuery("""
      SELECT sql FROM sqlite_master WHERE type='table' AND name='$tableName' COLLATE NOCASE limit 1
    """);
    String sql = result[0]["sql"] as String;
    int startIndex = sql.indexOf("(") + 1;
    int endIndex = sql.indexOf(")");
    sql = sql.substring(startIndex, endIndex);

    List<String> sqlList = sql.split(",").map((e) => e.trim()).toList();
    bool exists = false;
    for (int j = 0; j < sqlList.length; j++) {
      var rowStr = sqlList[j].trim().split(",").join("");
      var colName = rowStr.split(" ")[0].trim();
      if (colName == columnName) {
        exists = true;
        break;
      }
    }
    return exists;
  }

  ///新增列
  Future addColumn(String columnName, String type) async {
    return await _database!.rawQuery("""
      ALTER TABLE $tableName ADD  $columnName $type
    """);
  }

  ///删表
  dropTable() async {
    if (_database == null) {
      await _initDatabase();
    }
    await _database!.execute("""
      drop table if exists $tableName;
    """);
  }

  Database get database => _database!;

  ///插入数据
  insert(Map<String, Object?> values) async {
    return database.insert(tableName, values);
  }

  ///删除数据
  remove(Map<String, Object?> json) async {
    List<String> keys = json.keys.toList();
    List<String> where = [];
    for (int i = 0; i < keys.length; i++) {
      String key = keys[i];
      where.add("$key=${json[key]}");
    }

    return database.delete(
      tableName,
      where: where.join(" and "),
    );
  }

  ///修改数据
  update(Map<String, Object?> json1, Map<String, Object?> json2) async {
    List<String> keys = json1.keys.toList();
    List<String> where = [];
    for (int i = 0; i < keys.length; i++) {
      String key = keys[i];
      if (json1[key].runtimeType == String) {
        where.add("$key='${json1[key]}'");
      } else {
        where.add("$key=${json1[key]}");
      }
    }

    return database.update(
      tableName,
      json2,
      where: where.isEmpty ? null : where.join(" and "),
    );
  }

  ///缓存的数据
  static final Map<String, List<Map<String, Object?>>> _findCache = {};

  ///查找数据
  Future<List<Map<String, Object?>>> find({
    Map<String, dynamic>? where,
    int? page,
    int? pageSize,
  }) async {
    List<String> keys = where?.keys.toList() ?? [];

    List<String> whereList = [];
    for (int i = 0; i < keys.length; i++) {
      String key = keys[i];
      if (where![key].runtimeType == String) {
        whereList.add("$key='${where[key]}'");
      } else {
        whereList.add("$key=${where[key]}");
      }
    }

    String sql = whereList.join(" and ");
    String mapKey = "${tableName}_${sql}_page=${page}_pageSize=$pageSize";

    List data = sql.isEmpty ? [] : (_findCache[mapKey] ?? []);
    if (data.isNotEmpty) {
      return _findCache[mapKey]!;
    }

    var result = await database.query(
      tableName,
      where: sql.isEmpty ? null : sql,
      offset: page == null ? null : (page - 1) * (pageSize ?? 1),
      limit: pageSize,
    );
    if (sql.isNotEmpty) {
      _findCache[mapKey] = result;
    }
    return result;
  }

  rawQuery(String sql) async {
    return database.rawQuery(sql);
  }
}

转载请注明:汇站网 » 二次封装 Sqlite 基类(Flutter 教程)

收藏 (0)

微信扫一扫

支付宝扫一扫

点赞 (0)

感谢您的来访,获取更多精彩资源请收藏本站。

本站声明

本资源仅用于个人学习和研究使用,禁止用于任何商业环境!

 1.  本网站名称:汇站网
 2.  本站永久网址:https://www.huizhanii.com/
 3.  本站所有资源来源于网友投稿和高价购买,所有资源仅对编程人员及源代码爱好者开放下载做参考和研究及学习,本站不提供任何技术服务!
 4.  未经原版权作者许可,禁止用于任何商业环境,任何人不得擅作它用,下载者不得用于违反国家法律,否则发生的一切法律后果自行承担!
 5.  为尊重作者版权,请在下载24小时内删除!请购买原版授权作品,支持你喜欢的作者,谢谢!
 6.  若资源侵犯了您的合法权益, 请持您的版权证书和相关原作品信息来信通知我们请来信     通知我们我们会及时删除,给您带来的不便,我们深表歉意!
 7.  如下载链接失效、广告或者压缩包问题请联系站长处理!
 8.  如果你也有好源码或者教程,可以发布到网站,分享有金币奖励和额外收入!
 9.  本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
 10.  因源码具有可复制性,一经赞助 ,不得以任何形式退款。
 11.  更多详情请点击查看

汇站网 flutter 二次封装Sqlite基类(Flutter 教程) https://www.huizhanii.com/33895.html

汇站

站长资源下载中心-找源码上汇站

常见问题
  • 如果付款后没有弹出下载页面,多刷新几下,有问题联系客服!
查看详情
  • 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。
查看详情

相关文章

发表评论
暂无评论
  随机评论 表情开关按钮图片
表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情表情
登录后评论
联系官方客服

为您解决烦忧 - 24小时在线 专业服务

(汇站网)一个专注站长资源的平台网站,提供最新的网站模板和整站源码,内容包含各类精品网页模板,企业网站模板,网站模板,DIV+CSS模板,织梦模板,帝国cms模板,discuz模板,wordpress模板,个人博客论坛模板,上千种免费网页模板下载尽在汇站网.找源码上汇站.huizhanii.com