原文 https://levelup.gitconnected.com/top-5-local-database-solutions-for-flutter-development-6351cd494070
这里列出了最流行的数据库解决方案以及代码示例。
选择正确的数据管理系统对于提高效率和可 extension 性以及影响可用性和用户体验至关重要。尽管 flutter 仍然处于早期阶段,但是有很多数据管理解决方案可供选择,其中一些已经可以投入生产。我将概述用于在本地维护数据的最常见的数据库管理系统。
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 是一个非常有用的 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> findAllPersons();
@Query('SELECT * FROM Person WHERE id = :id')
Stream 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 来避免。
总的来说,地板是一个有前途的平方弗莱特包装与许多有用的功能。有关详细信息,请参阅他们的文档。它通过示例和图表清楚地展示了所有特性。
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> 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 是一个非常强大和有前途的 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 是另一个用于 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 支持“触发器”和“数据加密”这两个有用的特性可以帮助提高数据的安全性和一致性。
如果您的应用程序不是数据关键型的,那么您就不需要花费大量时间学习额外的库。共享首选项使用特定于平台的存储 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 解决方案之间的区别。它们都有优点和缺点,但这不是重点。因为我们不能说一个比另一个好。归根结底就是你的需要。
「关系数据库 Relational DBMS:」
「NoSQL:」
这些是两个数据库系统的关键特性。当涉及到使用本地数据库进行开发时,您应该彻底理解应用程序的数据需求。
如果您正在开发同时具有数字产品和实体产品的商店应用程序,那么这些产品可能具有多种特性。即使是同一类别的产品也可以有不同的特点。在这种情况下,您需要一个 NoSQL 数据库,否则最终会得到一大堆可为空的列。
考虑另一个示例应用程序,比如加密货币投资组合应用程序。当然,您的应用程序将依赖于第三方 API。这些 API 经常更改。您可以使用 NoSQL 数据库,而不是每次都更改模式,这比使用关系型 DBMS 更容易适应更改。我相信你明白这一点,但是你可以阅读更多关于这一主题的文章,以帮助你为未来的项目做出更好的决策。
我试图简化用于 flutter 开发的最流行的本地数据库解决方案的基本原理。这些都是非常棒的软件包,背后都有非常聪明的头脑。试着通过给他们星级或者为他们的知识库做贡献来帮助他们。我希望这篇文章对你决定下一个数据库解决方案有帮助。
最后一个想法。我很了解「幻界」和「伊萨」的数据库我知道他们正在崛起。这里没有列出它们,因为它们最近才发布了 alpha 版本。
谢谢你宝贵的时间。
如果本文对你有帮助,请转发让更多的朋友阅读。
也许这个操作只要你 3 秒钟,对我来说是一个激励,感谢。
祝你有一个美好的一天~
© 猫哥
微信 ducafecat
https://wiki.ducafecat.tech
https://video.ducafecat.tech
本文由 mdnice 多平台发布