首先我们需要安装 Sqlite 插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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; } } |
在基类中我们实现了建表的功能, 同理数据库升级或降级也可以这样写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
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!; } } |
好了现在我们有了建表的功能, 但是我们还需要对表进行增删改查, 所以接下来我们封装一个简易的增删改查功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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(); } } |
此时我们需要考虑一些问题:
基于以上问题, 我们可以将 UserInfoEntity 改成单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
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) {} } |
然后再新建一个类,用来存放所有实体类, 将所有实体类先进行实例化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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, 等数据库初始化完成, 并且所有的表都已经创建完成, 再执行下面的代码
void main() async { await MySqlite.forFeature(); runApp(const MyApp()); } |
需要注意一个问题, 等待数据库和表的初始化完成是需要时间的, 这种方式会阻塞页面, 如果表很多的话白屏时间会加长, 可以将这个方法放在启动页中, 等初始化完毕后再进入主页面, 因为我的程序数据表并不多, 所以我就直接写在mian函数中了;
此时再页面中可以直接对数据库进行增删改查的操作了
UserInfoEntity userInfoEntity = UserInfoEntity.instan(); userInfoEntity.insert({ "name": "刘小明", "sex": 1, "phone": "123456789", }); userInfoEntity.find(); userInfoEntity.update(); userInfoEntity.remove({"id": 1}); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
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); } } |
作者:向往全栈的Web小白