Flutter 5 大本地数据库解决方案
原文 levelup.gitconnected.com/top-5-local…
前言
这儿列出了最盛行的数据库解决方案以及代码示例。
挑选正确的数据办理体系对于进步功率和可 extension 性以及影响可用性和用户体会至关重要。虽然 flutter 依然处于前期阶段,可是有很多数据办理解决方案可供挑选,其中一些已经能够投入生产。我将概述用于在本地保护数据的最常见的数据库办理体系。
正文
Sqflite
Sqflite 是一个闻名的 SQLite flutter 插件。这是一个具有良好买卖和批量支撑的联系数据库。当数据库翻开时,它会自动办理版别操控。它还包含用于常见 CRUD 操作的协助器。后台线程处理一切数据库操作。它与 ACID 兼容,因而简直支撑一切 SQL 规范。假如您喜爱将自己的 SQL 查询编写为字符串,那么这个简略的插件将满意您的数据办理需求。
// open the database
Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
// When creating the db, create the table
await db.execute(
'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)');
});
// Insert some records in a transaction
await database.transaction((txn) async {
int id1 = await txn.rawInsert(
'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');
print('inserted1: $id1');
int id2 = await txn.rawInsert(
'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)',
['another name', 12345678, 3.1416]);
print('inserted2: $id2');
});
// Update some record
int count = await database.rawUpdate(
'UPDATE Test SET name = ?, value = ? WHERE name = ?',
['updated name', '9876', 'some name']);
print('updated: $count');
// Get the records
List<Map> list = await database.rawQuery('SELECT * FROM Test');
List<Map> expectedList = [
{'name': 'updated name', 'id': 1, 'value': 9876, 'num': 456.789},
{'name': 'another name', 'id': 2, 'value': 12345678, 'num': 3.1416}
];
正如您从示例中看到的,我发现阅览这段代码并不简单。跟着应用程序的增长,保护此代码将变得十分繁琐。由于 sqflite 是一个根本的数据库办理体系(DBMS)插件,所以我信任您应该构建自己的结构并将其包装在 sqflite 周围,就像大多数用于 flutter 的联系数据库办理体系包相同。
SQLite 通常是一个自包含的、无服务器的、轻量级的解决方案。它的性能是有争议的,但它能够让你的工作与一个耀眼的快速内存数据库。根底软件包包含移动渠道支撑。没有网络支撑,但 sqflite_common_ffi 能够用来支撑桌面渠道。
floor
Floor 是一个十分有用的 SQLite 抽象,它包含一个目标映射器。它依赖于 sqflite,并在此根底上增加了类型安全等特性。它支撑 sqflite 支撑的一切内容,包含内存数据库。
虽然它充任 sqflite 的包装器,但它引入了更高档的概念,如 DAO 和实体。实体能够用来将内存中的目标映射到数据记载,而 DAO 答应您拜访和操作数据。清楚地分隔实体、 DAO 和数据库总是一个好主意。您还能够为您的 DAO 编写好的测验,并确保您的查询经过这种方式得到验证。
@entity
class Person {
@primaryKey
final int id;
final String name;
Person(this.id, this.name);
}
前面的代码示例演示怎么创立实体。地板是指导创立适当的数据库表运用注释。这是一种十分用户友爱的规划数据库的办法。让咱们来看看怎么构建 DAO。
@dao
abstract class PersonDao {
@Query('SELECT * FROM Person')
Future<List<Person>> findAllPersons();
@Query('SELECT * FROM Person WHERE id = :id')
Stream<Person?> findPersonById(int id);
@insert
Future<void> insertPerson(Person person);
}
您所要做的便是供给一个具有适当注释的抽象类。遗憾的是,没有查询 API,因而依然有必要以字符串方式供给 SQL 查询。这种办法对我来说特别没有吸引力,由于这些查询没有可用的语法查看。您依然能够测验和验证这些查询。
final personDao = database.personDao;
final person = Person(1, 'Frank');
await personDao.insertPerson(person);
final result = await personDao.findPersonById(1);
地板是生成必要的代码在一个建设者的协助下。因而,一切的 DAO 都存储在数据库中。要插入一个人,您能够调用 database. person. insert tPerson (person)。老实说,我不以为经过数据库目标拜访一切 DAO 是一个好主意。然而,这种丑陋能够经过运用依赖注入(DI) container 来避免。
总的来说,地板是一个有出路的平方弗莱特包装与许多有用的功用。有关详细信息,请参阅他们的文档。它经过示例和图表清楚地展现了一切特性。
Drift (Moor)
Moor 无疑是功用最丰富的 Flutter 联系数据库解决方案。它也是 sqflite 的包装器,可是它供给了更强壮的功用,并且包含一个查询 API。虽然 Web 支撑是实验性的,但它支撑一切潜在的渠道。此外,它还为业务、形式迁移、杂乱的挑选器和表达式以及批处理供给了极好的支撑。
它为 DAO 和实体(如 floor)供给了类似的支撑,可是它是以一种高度模块化的方式进行的,答应您轻松地将它与 DI container 绑定结合起来。此解决方案的另一个优秀特性是内置的线程支撑,它答应您在不需求额定工作的情况下跨阻隔区运行数据库代码。最终,值得注意的是,它包含一个用于 SQL 查询的集成 IDE。多酷啊!
让咱们看一些代码示例,以便您能够将其与 floor 进行比较。下面是一个怎么界说表的示例。
class Todos extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text().withLength(min: 6, max: 32)();
TextColumn get content => text().named('body')();
IntColumn get category => integer().nullable()();
}
咱们不运用注释,而是 extension Table 类,并为该列的数据类型运用特定的列类型。在这儿,咱们能够为独自的列装备各种属性,比方空性、外键和约束。说到桌子的界说,我有必要供认我喜爱地板的方式。它仅仅对我来说更具可读性,并且注释的运用是一个绝妙的概念。
这便是 DAO 的界说方式。
@DriftAccessor(tables: [Todos])
class TodosDao extends DatabaseAccessor<MyDatabase> with _$TodosDaoMixin {
// this constructor is required so that the main database can create an instance
// of this object.
TodosDao(MyDatabase db) : super(db);
Stream<List<TodoEntry>> todosInCategory(Category category) {
if (category == null) {
return (select(todos)..where((t) => isNull(t.category))).watch();
} else {
return (select(todos)..where((t) => t.category.equals(category.id)))
.watch();
}
}
}
毫无疑问,您首要注意到的是查询 API,而不是基于文本的 sql 查询。就个人而言,我喜爱查询 API,由于跟着项目的 extension ,您会发现一些过滤器或数据拜访办法或许会被简化。您能够在不牺牲查询可读性的情况下减少代码复制。我理解,经过 SQLite 语法学习另一种 API 或许不是最舒服的挑选,但它肯定是值得的。我很欣赏这样的主意,即完好的数据库逻辑能够用 Dart 编写,并且在编译过程中能够检测到或许的错误。
当我需求运用联系型本地数据库时,漂移是我的第一挑选。它仅仅供给了一种更有用的开发和伸缩方式。由于该插件运用代码生成,所以我信任实体和 DAO 声明或许更加用户友爱。该项目仍在活跃开发中,每个新版别都比前一个版别有所改进。
hive
Hive 是一个十分强壮和有出路的 NoSQL 数据库。它与一切渠道兼容,包含网络。我没有第一手的知识,可是快速的查找显现读写速度的基准是十分令人形象深入的。它有很强的内置加密。将其视为一个映射,其中目标以键-值对的方式存储。
由于它是一个 NoSQL 数据库,术语“ table”被替换为术语“ box”(kind of)。因而,您的数据被组织在这些框中。由于 NoSQL 具有很强的习惯性,所以您能够为每个值界说任何类型的结构。因而,框的存在并不意味着存在共同的数据模型。假如您的应用程序需求为相同类型的实体供给灵敏的数据列,那么这或许是一个重要的好处。
在运用框之前,有必要首要翻开它,以便将数据从本地存储加载到内存中以便进行即时拜访。因而,您能够在不运用“ wait”的情况下进行查询,并且在 widget 的构建办法中运用它将十分简略。假如你不需求当即拜访,你能够随时运用懒箱。
var box = Hive.box('products');
box.put('name', 'foo');
var name = box.get('name');
print('Product Name: $name');
您能够看到怎么运用上面的 Hive 创立一个盒子。这不难理解。Hive 为一切根本类型供给了根本支撑,但大多数情况下,您期望存储的是实体或实体的子集。为此,有必要首要创立 TypeAdapter。这儿有一个比方:
@HiveType(typeId: 0)
class Product extends HiveObject {
@HiveField(0)
String name;
@HiveField(1)
int price;
@HiveField(2)
String color;
}
将 HiveObjects 视为实体。不过,在这种情况下,您能够 extension Product 并将其存储在同一个框中。下面是怎么运用 Product 类的示例。
var box = await Hive.openBox('products');
var product = Product()
..name = 'Foo'
..price = 10
..color = 'orange';
box.add(product);
print(box.getAt(0)); // Foo - 10 - orange
product.price = 12;
product.save();
print(box.getAt(0)) // Foo - 12 - orange
在我看来,蜂巢是小规模和灵敏应用程序的理想挑选。它易于学习并习惯改变。我很高兴能把它用在一个副业上。
sembast
Sembast 是另一个用于 Flutter (和 Dart )应用程序的 NoSQL 数据库,它支撑一切或许的渠道,包含 web (经过 Sembast web 包)。Sembast 由于其有出路的功用而在这个名单上,但我不信任它还在那里。它以类似于 Hive 的方式办理数据库,但有一些不同之处。我没有看到任何对实体的支撑,我以为这在数据形式可读性方面是有利的。您依然能够运用自己的类型,但它们有必要是正确编码/解码的 JSON。
// File path to a file in the current directory
String dbPath = 'sample.db';
DatabaseFactory dbFactory = databaseFactoryIo;
// We use the database factory to open the database
Database db = await dbFactory.openDatabase(dbPath);
若要运用数据库,有必要运用。分贝 extension 。此文件将用于一切数据库操作,并将根据需求进行更新。假如你不想运用这个选项,你依然能够经过 sqflite 运用 Sembast。
// dynamically typed store
var store = StoreRef.main();
// Easy to put/get simple values or map
// A key can be of type int or String and the value can be anything as long as it can
// be properly JSON encoded/decoded
await store.record('title').put(db, 'Simple application');
await store.record('version').put(db, 10);
await store.record('settings').put(db, {'offline': true});
// read values
var title = await store.record('title').get(db) as String;
var version = await store.record('version').get(db) as int;
var settings = await store.record('settings').get(db) as Map;
// ...and delete
await store.record('version').delete(db);
在上面,您能够看到怎么保存和读取记载。API 学起来很简略,但我更喜爱 Hive 语法而不是 Sembast 语法,由于它省去了在读取数据时“等候”的需求。可是,你依然能够探究森巴斯特。值得注意的是,Sembast 支撑“触发器”和“数据加密”这两个有用的特性能够协助进步数据的安全性和共同性。
shared_preferences
假如您的应用程序不是数据要害型的,那么您就不需求花费大量时间学习额定的库。共享首选项运用特定于渠道的存储 API 来存储和读取键值对。由于数据能够异步地保存到磁盘上,所以这个插件不应该用来存储要害数据。
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
print('Pressed $counter times.');
await prefs.setInt('counter', counter);
}
正如您所看到的,运用该插件是简略和直接的。
联系数据库或 NoSQL
我觉得有义务解说联系数据库办理体系和 NoSQL 解决方案之间的差异。它们都有长处和缺点,但这不是重点。由于咱们不能说一个比另一个好。归根结底便是你的需求。
联系数据库 Relational DBMS:
- 数据共同性
- 数据形式不太或许发生明显改变
- 存储实践数据和数值数据
NoSQL:
- 数量巨大,数据混乱
- 灵敏的形式界说,实时数据,更简单伸缩
- 没有衔接,查询速度更快,但数据复制更多
这些是两个数据库体系的要害特性。当涉及到运用本地数据库进行开发时,您应该彻底理解应用程序的数据需求。
假如您正在开发同时具有数字产品和实体产品的商店应用程序,那么这些产品或许具有多种特性。即使是同一类别的产品也能够有不同的特点。在这种情况下,您需求一个 NoSQL 数据库,否则最终会得到一大堆可为空的列。
考虑另一个示例应用程序,比方加密货币投资组合应用程序。当然,您的应用程序将依赖于第三方 API。这些 API 常常更改。您能够运用 NoSQL 数据库,而不是每次都更改形式,这比运用联系型 DBMS 更简单习惯更改。我信任你理解这一点,可是你能够阅览更多关于这一主题的文章,以协助你为未来的项目做出更好的决议计划。
Conclusion
结论
我企图简化用于 flutter 开发的最盛行的本地数据库解决方案的根本原理。这些都是十分棒的软件包,背面都有十分聪明的脑筋。试着经过给他们星级或许为他们的知识库做贡献来协助他们。我期望这篇文章对你决定下一个数据库解决方案有协助。
最终一个主意。我很了解「幻界」和「伊萨」的数据库我知道他们正在崛起。这儿没有列出它们,由于它们最近才发布了 alpha 版别。
谢谢你宝贵的时间。
结束语
假如本文对你有协助,请转发让更多的朋友阅览。
或许这个操作只要你 3 秒钟,对我来说是一个激励,感谢。
祝你有一个美好的一天~
猫哥
-
微信 ducafecat
-
wiki.ducafecat.tech
-
video.ducafecat.tech