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
|
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
|
import 'package:floor/floor.dart';
@dao abstract class PersonDao {
@Insert(onConflict: OnConflictStrategy.replace) Future<void> insertPerson(Person person);
@Update(onConflict: OnConflictStrategy.replace) Future<int> updatePerson(Person person);
@Query('SELECT * FROM Person WHERE id = :id') Future<Person?> findPersonById(int id);
@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
|
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';
@TypeConverters([ DateTimeConverter, NullableDateTimeConverter ]) @Database(version: 1, entities: [Person]) abstract class AppDatabase extends FloorDatabase { PersonDao get personDao;
static Future<AppDatabase> initDatabase() async { final database = await $FloorFitTrackDataBase .databaseBuilder('test.db') .addCallback(callback) .build(); return database; } }
final callback = Callback( onCreate: (database, version) {}, onOpen: (database) {}, onUpgrade: (database, startVersion, endVersion) {}, );
|
最后使用以下命令来运行生成器:
1
| flutter packages pub run build_runner build
|
注:part 'database.g.dart'
要先在文件中引入,然后再运行build_runner
的 build 命令