正文:
在Flutter中,我们可以使用 Sqlite 作为本地数据库来存储和管理数据。为了方便使用,我们可以进行二次封装,创建一个基类来处理常见的数据库操作。
安装 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 教程)