Szhangbiao's blog

记录一些让自己可以回忆的东西

0%

Flutter本地存储之Floor

floor 是 Flutter 上使用 SQLite 数据库的 ORM 框架。框架的思想借鉴了 Android Jetpack 中的Room框架,总体使用下来语法上几乎一致,对 Android 开发来说比较友好。

安装

1
2
3
4
5
6
7
8
dependencies:
flutter:
sdk: flutter
floor: ^1.4.2 // 包含了用于运行时数据库操作的类和方法

dev_dependencies:
floor_generator: ^1.4.2 // 根据实体和 DAO 的定义生成数据库操作代码
build_runner: '>=2.3.0 <4.0.0' // Flutter 系统的生成代码工具,配合floor_generator生成数据库操作代码

在经过 build_runner 的命令后生成的代码中还需要 sqflite插件,它提供了在 Flutter 中与 SQLite 数据库进行底层交互的功能,负责管理 SQLite 数据库文件、执行原始 SQL 查询和处理数据库事务等。

ORM 架构体系

框架的组件主要有:实体、数据访问对象(DAO) 和 数据库:

  • 一个 实体 代表一个 持久类,也就是一个 数据库表;
  • Dao 管理对实体的访问,并负责内存中 对象 和表行 之间的 映射;
  • 数据库是底层 SQLite 数据库的中心访问点,除此之外,还负责数据库的创建、打开、关闭和升级操作。

Floor 支持的数据类型

Dart 类型 SQLite 类型
int INTEGER
double REAL
String TEXT
bool INTEGER(0 = false,1 = true)
Uint8List BLOB
enum INTEGER(按索引 0..n 记录)

如果需要支持自定义类型则需要实现class TypeConverter<T, S>类来做类型转换,然后添加到TypeConverters注解中就可以了

项目实战中的使用

首先是实体类:

  • @entity 注解用于标注实体类对应为数据库中的表,类的属性对应为表的列,@Entity 注解可以指定表名、添加外键和索引等设置。
  • @primaryKey 注解用于标注实体类中的主键列。当主键的数据类型是 int 且@@PrimaryKey 注解的参数 autoGenerate 为 ture 时,SQLite 可以自动生成该值。
  • @ColumnInfo 注解用于给表中的列设置别名。
  • @ignore 注解用于忽略某个属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// database/entity/person.dart

import 'package: floor/floor.dart';

@entity
class Person {
@primaryKey
final int id;
final String name;
@ColumnInfo(name: 'nick_name')
final String nickName;
@ColumnInfo(name: 'date_created')
DateTime? dateCreated;
@ColumnInfo(name: 'date_modified')
DateTime? dateModified;

@ignore
final bool isFriend;
Person(this.id, this.name, this.nickName, this.dateCreated, this.dateModified, this.isFriend);
}

其次是 Dao 类:
负责管理对底层的 SQLite 数据库的访问。抽象类包含了查询数据库的方法签名,这些方法必须返回 Future 或 Stream。

  • @insert 标记 Dao 类的方法是用于插入数据的,如果插入的数据跟表中的数据有冲突默认会中止,我们可以通过@Insert(onConflict: XXX)来设置冲突策略,一般有三种策略:替换、撤回、中止、失败和忽略。
  • @update 标记 Dao 类的方法是用于更新数据的,@Update 的冲突策略跟@Insert 一致。
  • @Query 标记 Dao 类的方法是用于查询数据的,注解里的value就是查询 SQL 语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// database/dao/person_dao.dart

import 'package:floor/floor.dart';

@dao
abstract class PersonDao {

// 将提供的 Person 对象插入到数据库中
@Insert(onConflict: OnConflictStrategy.replace)
Future<void> insertPerson(Person person);

// 将提供的 Person 对象更新到数据库中
@Update(onConflict: OnConflictStrategy.replace)
Future<int> updatePerson(Person person);

// 根据提供的 id 查询并返回匹配的 Person 实体对象
@Query('SELECT * FROM Person WHERE id = :id')
Future<Person?> findPersonById(int id);

// 查询并返回所有的 Person 实体对象列表
// 这是一个异步数据流,当数据发生变化时,会自动更新
@Query('SELECT * FROM Person')
Stream<List<Person>> findAllPersons();
}

最后是创建数据库:

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
// database/app_database.dart

import 'package:floor/floor.dart';
import 'package:sqflite/sqflite.dart' as sqflite;

import 'dao/person_dao.dart';
import 'entity/person.dart';

part 'database.g.dart'; // Floor代码生成器将在这里生成相关的代码

@TypeConverters([
DateTimeConverter,
NullableDateTimeConverter
])
@Database(version: 1, entities: [Person]) // 声明数据库版本号和包含的实体类
abstract class AppDatabase extends FloorDatabase {
PersonDao get personDao; // 获取PersonDao实例,用于执行数据库操作

/**
* 初始化数据库,提供static方法是为了调用方便也可以做成单例
*/
static Future<AppDatabase> initDatabase() async {
final database = await $FloorFitTrackDataBase
.databaseBuilder('test.db')
// .addMigrations([migration1to2]) // 执行数据升级表结构的变动的SQL语句
.addCallback(callback) // 数据库操作的回调,如创建、打开、升级等
.build();
return database;
}
}

final callback = Callback(
onCreate: (database, version) {/* 已创建数据库 */},
onOpen: (database) {/* 数据库已打开*/},
onUpgrade: (database, startVersion, endVersion) {/* 数据库已升级 */},
);

// 创建一个migration
// final migration1to2 = Migration(1, 2, (database) async {
// database.execute('SQL');
// });

最后使用以下命令来运行生成器:

1
flutter packages pub run build_runner build

注:part 'database.g.dart'要先在文件中引入,然后再运行build_runner的 build 命令