tinano
Tinano 是一个基于 sqflite 的 Flutter 应用本地持久化库。虽然 sqflite 是一个很棒的数据持久化库,但编写所有解析代码本身很繁琐,而且很容易出错。
使用 Tinano,您可以指定查询及其返回的数据结构,它将自动处理所有手动和繁琐的工作,为您提供一种干净且类型安全的方式来管理应用数据。
入门
设置您的项目
首先,让我们准备您的 pubspec.yaml 来添加此库和工具
用于根据数据库定义自动生成代码。
dependencies:
tinano:
# ...
dev_dependencies:
tinano_generator:
build_runner:
# test, ...
tinano 库将为您提供一些注解来编写您的
数据库类,而 tinano_generator 则插入 build_runner
来生成实现。由于我们只在开发过程中进行代码生成(而不是在运行时),所以这两个都可以作为开发依赖项。
创建数据库
使用 Tinano,创建数据库很简单
您的数据库类必须是抽象的,并且有一个名为 createBuilder 的静态方法,该方法使用 => 语法。_$createMyDatabase() 方法稍后将自动生成。当然,您可以自由选择任何名称,但创建数据库的方法必须以 _$ 开头。
import 'package:tinano/tinano.dart';
import 'dart:async';
part 'database.g.dart'; // this is important!
@TinanoDb(name: "my_database.sqlite", schemaVersion: 1)
abstract class MyDatabase {
static DatabaseBuilder<MyDatabase> createBuilder() => _$createMyDatabase();
}
您的数据库类必须是抽象的,并且有一个名为 createBuilder 的静态方法,该方法使用 => 语法。_$createMyDatabase() 方法稍后将自动生成。当然,您可以自由选择任何名称,但创建数据库的方法必须以 _$ 开头。
您的数据库类必须是抽象的,并且有一个名为 createBuilder 的静态方法,该方法使用 => 语法。_$createMyDatabase() 方法稍后将自动生成。当然,您可以自由选择任何名称,但创建数据库的方法必须以 _$ 开头。
您的数据库类必须是抽象的,并且有一个名为 createBuilder 的静态方法,该方法使用 => 语法。_$createMyDatabase() 方法稍后将自动生成。当然,您可以自由选择任何名称,但创建数据库的方法必须以 _$ 开头。
当然,您可以自由选择任何名称,但创建数据库的方法必须以 _$ 开头。
当然,您可以自由选择任何名称,但创建数据库的方法必须以 _$ 开头。
目前,这段代码将导致一堆错误,因为实现
尚未生成。在终端中快速运行 flutter packages pub run build_runner build
将解决此问题。如果您想在每次更改规范时自动重建数据库实现(在开发过程中可能很有用),可以使用 flutter packages pub run build_runner watch。
如果您想在每次更改规范时自动重建数据库实现(在开发过程中可能很有用),可以使用 flutter packages pub run build_runner watch。
如果您想在每次更改规范时自动重建数据库实现(在开发过程中可能很有用),可以使用 flutter packages pub run build_runner watch。
打开数据库
要获取 MyDatabase 的实例,您只需像这样使用构建器函数
像这样
Future<MyDatabase> openMyDatabase() async {
return await (MyDatabase
.createBuilder()
.doOnCreate((db, version) async {
// This await is important, otherwise the database might be opened before
// you're done with initializing it!
await db.execute("""CREATE TABLE `users` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL )""");
})
.build());
}
doOnCreate 块将在数据库首次打开时执行。db 参数将使您能够访问原始 sqflite 数据库,version 参数是您的 @TinanoDb 注解中指定的架构版本。db 参数将使您能够访问原始 sqflite 数据库,version 参数是您的 @TinanoDb 注解中指定的架构版本。
db 参数将使您能够访问原始 sqflite 数据库,version 参数是您的 @TinanoDb 注解中指定的架构版本。
您可以使用 addMigration 方法执行架构迁移 - 有关
更多信息请参见下方。
数据库查询
当然,仅仅打开数据库是很无聊的。为了实际
执行一些数据库查询,只需创建用 @Query、@Update、@Delete 或 @Insert 注解的方法即可。下面是一个符合
上面定义的 doOnCreate 方法的示例
上面定义的 doOnCreate 方法的示例
@TinanoDb(name: "my_database.sqlite", schemaVersion: 1)
abstract class MyDatabase {
static DatabaseBuilder<MyDatabase> createBuilder() => _$createMyDatabase();
@Query("SELECT * FROM users")
Future<List<UserRow>> getAllUsers();
// If we know we'll only get one user, we can skip the List<>. Note that this
// really expects there to be one row -> if there are 0, it will throw an
// exception.
@Query("SELECT * FROM users WHERE id = :id")
Future<UserRow> getUserById(int id);
// For queries with only one column that is either a String, a num or a
// Uint8List, we don't have to define a new class.
@Query("SELECT COUNT(id) FROM users")
Future<int> getAmountOfUsers();
// Inserts defined to return an int will return the insert id. Could also
// return nothing (Future<Null> or Future<void>) if we wanted.
@Insert("INSERT INTO users (name) VALUES (:name)")
Future<int> createUserWithName(String name);
// Inserts return values based on their return type:
// For Future<Null> or Future<void>, it won't return any value
// For Future<int>, returns the amount of changed rows
// For Future<bool>, checks if the amount of changed rows is greater than zero
@Update("UPDATE users SET name = :updatedName WHERE id = :id")
Future<bool> changeName(int id, String updatedName);
// The behavior of deletes is identical to those of updates.
@Delete("DELETE FROM users WHERE id = :id")
Future<bool> deleteUser(int id);
}
// We have to annotate composited classes as @row. They should be immutable.
@row
class UserRow {
final int id;
final String name;
UserRow(this.id, this.name);
}
变量
如您所见,您可以通过在 SQL 中直接使用 :myVariable 标记来轻松地将方法参数映射到 SQL
变量。如果您想在 SQL 中使用 : 字符,那是可以的,只需用反斜杠 \ 转义即可。请注意,您必须在 Dart 字符串中使用两个反斜杠("\\:")。
您必须在 Dart 字符串中使用两个反斜杠("\\:")。
您必须在 Dart 字符串中使用两个反斜杠("\\:")。
变量不会直接插入到查询中(这很容易导致 SQL 注入漏洞),而是使用预编译语句
先发送不带数据的 SQL,然后再发送变量。这意味着您
不能将变量用于所有内容,请参阅
https://www.quora.com/What-are-the-limitations-of-PDO-prepared-statements-Can-I-define-the-table-and-row-as-arguments">此处 了解一些不能使用变量的示例。
https://www.quora.com/What-are-the-limitations-of-PDO-prepared-statements-Can-I-define-the-table-and-row-as-arguments">此处 了解一些不能使用变量的示例。
架构更新
在 @TinanoDb 中提高版本号后,您将必须手动执行一些
迁移。您可以通过 DatabaseBuilder 直接进行迁移,方法是
使用 addMigration。
MyDatabase
.createBuilder()
.doOnCreate((db, version) {...})
.addMigration(1, 2, (db) async {
await db.execute("ALTER TABLE ....")
});
对于较大的迁移(例如从 1 到 5),只需为
每一步指定所有迁移。然后 Tinano 将按顺序应用它们,以确保数据库
在打开之前已准备就绪。
支持的类型
由于数据库访问是异步的,所有方法都必须返回一个 Future。
对于修改语句(更新/删除)
Future<int> 将解析为更新的行数,而
Future<bool> 作为返回类型将解析为 true(如果有任何更改)
和 false(如果没有)。
对于插入语句
Future<int> 将解析为最后插入的 ID。Future<bool>
将始终解析为 true,因此不建议在此处使用。
对于 select 语句
如果您想获取所有结果,必须使用 List<T>,或者如果您只想接收第一个结果,则直接使用 T。
请注意,无论哪种情况,整个响应最终都会加载到内存中,因此请在 SQL 中设置 LIMIT。
请在 SQL 中设置 LIMIT。
请在 SQL 中设置 LIMIT。
现在,如果您的结果只有一列,您可以使用该类型
直接使用。
@Query("SELECT COUNT(*) FROM users")
Future<int> getAmountOfUsers();
这适用于 int、num、Uint8List 和 String。请参阅
sqflite 的文档
以检查哪些 Dart 类型与哪些 SQLite 类型兼容。
如果您的查询返回多于一列,您必须将其定义
在一个新的不可变类中,该类只能有一个无参数构造函数来设置字段。
@row
class UserResult {
final int id;
final String name;
UserResult(this.id, this.name);
}
// this should be a method in your @TinanoDb class....
@Query("SELECT id, name FROM users")
Future<List<UserResult>> getBirthmonthDistribution();
每个 @row 类只能包含原始字段 int、num、
Uint8List 和 String。
访问原始数据库
如果您想使用 Tinano,但也有些用例需要直接使用sqflite 的 Database 来发送查询,您只需在 @TinanoDb 类中定义一个名为 Database database; 的字段即可。它将在数据库打开后生成并可用。
它将在数据库打开后生成并可用。
它将在数据库打开后生成并可用。
待办事项列表
- 如果我们能摆脱
doOnCreate,而在数据库类中直接定义这些
方法,并添加更多注解,那就太好了。这同样适用于迁移函数。
迁移函数。 - 批处理和事务以提高性能和可靠性。
- 自动更新返回
Stream并随着底层数据变化发出新值的查询。可能类似于
Android 上的 Room 库。
Android 上的 Room 库。 - 支持从库中直接支持
DateTime,自动生成代码将其存储
为时间戳在数据库中。 - 支持具有其他
@row类型作为字段的@row类。 - 支持自定义类作为变量参数,在 SQL 中指定类似
WHERE id = :user.id,然后将User user作为参数。 - 能够为 SQL 和 Dart 类型使用不同的变量/列名。
添加一些注解,如@FromColumn("my_column")。