Flutter笔记 用于ORM的Floor结构简记

因为掘金迁移和字数限制等诸多方面的问题,掘金版别或许丢掉图片、删去、着重符号乃至阶段,以及文字紊乱。推荐阅览链接原文

本文地址blog.csdn.net/qq_28550263…

floor 模块地址:pub.dev/packages/fl…


【介绍】:最近想找用于Dart和Flutter的ORM结构,偶然间发现了Floor,觉得还不错,做一些记载。

@[TOC](


)

1. Floor 结构概述

1.1 结构简介

Floor 结构是一个用于在 Flutter 应用程序中运用 SQLite 数据库的开源库。它答应开发者轻松地将本地数据库集成到其应用程序中,并供给了一种类型安全、高效和易于运用的办法来履行数据库操作。

1.2 结构的构成

Floor 结构由多个关键模块组成,每个模块都有特定的功能,协同作业以实现在 Flutter 应用程序中运用 SQLite 数据库的方针。以下是 Floor 结构的首要模块以及它们的效果:

  1. floor(运转时库):

    • 效果: floor 是 Floor 结构的运转时库,包括用于运转时数据库操作的类和办法。
    • 关键类和办法:
    • FloorDatabase:抽象类,表明数据库的根类,生成的数据库类需求扩展它。
    • @Database:用于符号生成的数据库类,指定数据库版别号、实体列表和其他装备信息。
    • databaseBuilder:用于创立数据库实例的办法。
    • 数据库操作办法:生成的数据库类中包括与数据库表的交互办法,例如刺进、查询、更新等。
  2. floor_generator(代码生成器):

    • 效果: floor_generator 是 Floor 结构的代码生成器模块,它会依据实体和 DAO 的界说生成数据库拜访代码。
    • 生成的代码: 该模块生成包括数据库类、DAO 类以及其他与数据库交互相关的代码,使开发者能够轻松履行数据库操作,而无需手动编写很多的重复代码。
  3. floor_annotation(Floor 结构中的注解)

    • 效果: floor_annotation是Floor 结构中的注解。这些注解用于符号和装备数据库实体(Entities)、数据拜访目标(DAOs)以及数据库操作,以便 Floor 结构的代码生成器 floor_generator 能够依据这些注解生成相应的数据库拜访代码。
    • floor_annotation 供给了一种类型安全的办法来装备和界说数据库操作,并答应开发者在 Dart 代码中运用注解来描绘数据库模型和操作,而无需手动编写很多的重复代码。
    • 例如:
      • @entity:用于符号 Dart 类,表明它是一个数据库实体(Entity)。实体类一般与数据库表相对应,用于界说表的结构和字段。
      • @dao:用于符号抽象类或接口,表明它是数据拜访目标(DAO)。DAO 类包括了与数据库表相关的操作办法,如刺进、查询、更新和删去。

等等

  1. sqflite(数据库驱动,由floor主动调用):
    • 效果: Floor 结构依赖于 sqflite 插件,它供给了在 Flutter 中与 SQLite 数据库进行底层交互的功能。
    • sqflite 插件担任管理 SQLite 数据库文件、履行原始 SQL 查询和处理数据库事务

这些模块协同作业,使开发者能够轻松地在 Flutter 应用程序中集成和操作 SQLite 数据库。经过界说实体、DAO 和数据库,然后运转代码生成器,开发者能够获得类型安全、高效和易于维护的数据库拜访代码,从而实现数据耐久化和本地数据存储的需求。

  1. build_runner:build_runner 一般与 floor_generator 一起运用,以主动生成与数据库操作相关的 Dart 代码。

1.3 安装

flutter pub add floor
flutter pub add floor_generator build_runner --dev

其中:

  • floor: 包括了将在应用程序中运用的一切代码。
  • floor_generator: 包括了生成数据库类的代码。
  • build_runner: 答应以具体的办法生成源代码文件。

将在你的yaml中看到:

dependencies:
  flutter:
    sdk: flutter
  floor: ^1.4.0
dev_dependencies:
  floor_generator: ^1.4.0
  build_runner: ^2.1.2

版别或许随时刻变化

1.4 架构体系

存储和拜访数据的组件是 实体数据拜访目标(DAO) 和 数据库

  • 一个 实体 代表一个 耐久类,也就是一个 数据库表
  • Dao管理对实体的拜访,并担任内存中 目标表行 之间的 映射
  • 数据库是底层 SQLite 数据库的中心拜访点。它 保存Dao,除此之外,还担任 初始化数据库及其 模式。

Flutter笔记:用于ORM的Floor框架简记

2. 实体(Entity)

一个实体是一个耐久化类。Floor 主动创立内存中目标与数据库表行之间的映射。能够经过向 Entity 注释增加可选值来向 Floor 供给自界说元数据。它具有表名 tableName 的附加特点,该特点打开了为特定实体运用自界说称号的或许性,而不是运用类名。foreignKeys 答应向实体增加外键。有关怎么运用这些内容的更多信息,请参阅外键部分。还支撑索引。能够经过将索引增加到实体的 indices 值来运用它们。

  • @entity 注解用于符号了该类为耐久类;
  • @PrimaryKey 将类的特点符号为主键列。该特点有必要是 int 类型。当启用 autoGenerate 时,SQLite 能够主动生成该值;
  • 有必要经过将 @primaryKey 注解增加到一个 int 特点上为你的表增加一个主键,例如:
    // entity/person.dart
    import 'package: floor/floor.dart';
    @entity
    class Person {
      @primaryKey
      final int id;
      final String name;
      Person(this.id, this.name);
    }
    
  • @ColumnInfo 使单个表列的自界说映射变得或许。运用该注释,能够为列指定自界说称号。假如要将表的列设置为可空,请将实体字段符号为可空;
  • Floor 主动运用实体类中界说的第一个结构函数来从数据库行创立内存中目标

支撑的类型

Floor 实体能够保存以下 Dart 类型的值,这些类型与它们对应的 SQLite 类型相互映射。

  • int – INTEGER
  • double – REAL
  • String – TEXT
  • bool – INTEGER(0 = false,1 = true)
  • Uint8List – BLOB
  • enum – INTEGER(按索引 0..n 记载)

假如要存储能够由上述类型之一表明的复杂 Dart 目标,请检查类型转换器。

主键

每当需求复合主键(例如 n-m 联系)时,设置主键的语法与前面说到的设置主键的办法不同。不是运用 @PrimaryKey 注释字段,而是运用 @Entity 注释的 primaryKey 特点。它承受一个由列名组成的列表,这些列名组成复合主键。

@Entity(primaryKeys: ['id', 'name'])
class Person {
  final int id;
  final String name;
  Person(this.id, this.name);
}

外键

向引证实体的 Entity 注释增加 ForeignKeys 列表。childColumns 界说当前实体的列,而 parentColumns 界说父实体的列。在 onUpdate 和 onDelete 特点中界说外键操作后,能够触发外键操作。

// Dog 类,表明数据库中的狗实体
@Entity(
  tableName: 'dog', // 指定表名为 'dog'
  foreignKeys: [
    ForeignKey(
      childColumns: ['owner_id'], // 子列名为 'owner_id'
      parentColumns: ['id'], // 父列名为 'id'
      entity: Person, // 引证的实体为 Person 类
    )
  ],
)
class Dog {
  @PrimaryKey() // PrimaryKey 注解,将 id 符号为主键列
  final int id;
  final String name; // 称号
  @ColumnInfo(name: 'owner_id') // ColumnInfo 注解,自界说列名为 'owner_id'
  final int ownerId; // 一切者的 ID
  // 结构函数,承受 id、name 和 ownerId 参数
  Dog(this.id, this.name, this.ownerId);
}
  • @Entity 注解符号了 Dog 类,指定了表名为 ‘dog’,一起指定了一个外键(foreign key),该外键将在 ‘owner_id’ 列上创立,它引证了另一个实体 Person。这意味着在数据库中将创立名为 ‘dog’ 的表,并在 ‘owner_id’ 列上创立一个外键,以保证数据的完整性。
  • @PrimaryKey 注解符号了 id 字段作为主键列,这意味着它在数据库中将用作主键。
  • @ColumnInfo 注解符号了 ownerId 字段,并自界说了列名为 ‘owner_id’,这答应在数据库表中运用不同的列名,而不是默认的字段名。
  • 结构函数承受 idnameownerId 参数,并用于创立 Dog 实例。

这些注释和注释说明了 Dog 类的结构,以及怎么装备表名、外键和自界说列名。

索引

索引有助于加快查询、连接和分组操作。有关 SQLite 索引的更多信息,请参阅官方文档。要在 floor 中创立索引,请将索引列表增加到 @Entity 注释。下面的示例演示了怎么在实体的 custom_name 列上创立索引。

此外,能够运用其 name 特点命名索引。要将索引设置为仅有索引,请运用 unique 特点。

// Person 类,表明数据库中的个人实体
@Entity(tableName: 'person', indices: [Index(value: ['custom_name'])])
class Person {
  @primaryKey // PrimaryKey 注解,将 id 符号为主键列
  final int id;
  @ColumnInfo(name: 'custom_name') // ColumnInfo 注解,自界说列名为 'custom_name'
  final String name; // 姓名
  // 结构函数,承受 id 和 name 参数
  Person(this.id, this.name);
}
  • @Entity 注解符号了 Person 类,指定了表名为 ‘person’,一起指定了一个索引,该索引将在 ‘custom_name’ 列上创立。这意味着在数据库中将创立名为 ‘person’ 的表,并在 ‘custom_name’ 列上创立一个索引,以提高查询功能。
  • @primaryKey 注解符号了 id 字段作为主键列,这意味着它在数据库中将用作主键。
  • @ColumnInfo 注解符号了 name 字段,并自界说了列名为 ‘custom_name’,这答应在数据库表中运用不同的列名,而不是默认的字段名。 结构函数承受 id 和 name 参数,并用于创立 Person 实例。

疏忽字段

默认情况下,实体的 getter、setter 和一切静态字段都会被疏忽,并因此从库的映射中扫除。假如要进一步疏忽字段,应运用 @ignore 注释,并按以下示例中所示应用它。

// 人员类,表明数据库中的人员实体
class Person {
  @primaryKey // PrimaryKey 注解,将 id 符号为主键列
  final int id;
  final String name; // 姓名
  @ignore // Ignore 注解,指定 nickname 字段将被疏忽
  String nickname; // 昵称
  // 默认情况下被疏忽的字段,不会包括在库的映射中
  String get combinedName => "$name ($nickname)";
  // 结构函数,承受 id 和 name 参数
  Person(this.id, this.name);
}

承继

与 Dao 一样,实体(和数据库视图)能够从共同的基类承继并运用它们的字段。实体只需扩展基类。这个结构将被视为假如基类的一切字段都是实体的一部分,这意味着数据库表将包括实体和基类的一切列。

基类不用为类单独注释。它的字段能够像正常的实体列一样进行注释。外键和索引有必要在实体中声明,不能在基类中界说。

// 基础目标类,用于承继到其他实体
class BaseObject {
  // 主键符号,指定 id 作为主键列
  @PrimaryKey()
  final int id;
  // ColumnInfo 注解,用于自界说列名 create_time
  @ColumnInfo(name: 'create_time')
  final String createTime;
  // ColumnInfo 注解,用于自界说列名 update_time
  @ColumnInfo(name: 'update_time')
  final String updateTime;
  // 结构函数,承受 id、updateTime 和可选的 createTime 参数
  BaseObject(
    this.id,
    this.updateTime, {
    String createTime,
  }) : this.createTime = createTime ?? DateTime.now().toString();
  // 重写父类的 props 办法,一般用于数据比较
  @override
  List<Object> get props => [];
}
// Comment 类,承继自 BaseObject 类
@Entity(tableName: 'comments') // Entity 注解,指定表名为 'comments'
class Comment extends BaseObject {
  final String author; // 谈论作者
  final String content; // 谈论内容
  // 结构函数,承受 author、可选的 id、content、createTime 和 updateTime 参数
  Comment(
    this.author, {
    int id,
    this.content = '', // 默以为空字符串
    String createTime,
    String updateTime,
  }) : super(id, updateTime, createTime: createTime); // 调用父类结构函数,传递 id、updateTime 和 createTime 参数
}

数据库视图(Database Views)

假如期望界说回来与实体不同类型的静态 SELECT 句子,最佳挑选是运用 @DatabaseView。数据库视图能够被理解为虚拟表,能够像实在表一样进行查询。

在 Floor 中,数据库视图的界说和运用办法与实体相似,首要差异在于只读拜访,这意味着无法履行更新、刺进和删去操作。与实体相似,假如未设置 viewName,则运用类名。

@DatabaseView('SELECT distinct(name) AS name FROM person', viewName: 'name')
class Name {
  final String name;
  Name(this.name);
}

数据库视图不具有任何外键、主键或索引。相反,您应该手动界说与您的句子匹配的索引,并将它们放入触及实体的 @Entity 注释中。

与实体相似,setter、getter 和静态字段将主动被疏忽(与实体相似),您能够运用 @ignore 注解来指定要疏忽的附加字段。

在代码中界说数据库视图后,您需求将它增加到 @Database 注释的 views 字段中:

@Database(version: 1, entities: [Person], views: [Name])
abstract class AppDatabase extends FloorDatabase {
  // DAO 获取器
}

然后,您能够经过 DAO 函数查询视图,就像查询实体一样。

数据库视图能够像实体一样从基类承继共同字段。

现在能够从 DAO 办法回来一个 Stream 目标,该办法查询数据库视图。但是,它将在整个数据库中的任何 @update@insert@delete 事情触发时发出,这或许会对运转时产生相当大的负担。请仅在明白自己在做什么时才增加它!这首要是因为检测哪些实体触及到数据库视图的复杂性所致。

3. 数据拜访目标(DAO – Data Access Object)

这个组件担任管理对底层的 SQLite 数据库的拜访。抽象类包括了查询数据库的办法签名,这些办法有必要回来 Future 或 Stream。

你能够经过将 @Query 注解增加到办法上来界说查询。SQL 句子有必要放在括号中。办法有必要回来你查询的实体的 Future 或 Stream。 @insert 符号了一个办法作为刺进办法。

// dao/person_dao.dart
import 'package:floor/floor.dart';
@dao
abstract class PersonDao {
  // 查询并回来一切的 Person 实体目标列表
  @Query('SELECT * FROM Person')
  Future<List<Person>> findAllPersons();
  // 依据供给的 id 查询并回来匹配的 Person 实体目标
  // 这是一个异步数据流,当数据发生变化时,会主动更新
  @Query('SELECT * FROM Person WHERE id = :id')
  Stream<Person?> findPersonById(int id);
  // 将供给的 Person 目标刺进到数据库中
  // 这是一个异步操作,不回来任何数据
  @insert
  Future<void> insertPerson(Person person);
}

4. 创立数据库

它有必要是一个承继自 FloorDatabase 的抽象类。此外,有必要在类的签名中增加 @Database()。保证将创立的实体增加到 @Database 注解的 entities 特点中。为了使生成的代码作业,还需求增加列出的导入项。

保证在这个文件的导入项下面增加 part ‘database.g.dart’;。重要的是要注意 ‘database’ 有必要与数据库界说的文件名进行交流。在这种情况下,文件名命名为 database.dart。

// database.dart
import 'dart:async';
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代码生成器将在这儿生成相关的代码
@Database(version: 1, entities: [Person]) // 声明数据库版别号和包括的实体类
abstract class AppDatabase extends FloorDatabase {
  PersonDao get personDao; // 获取PersonDao实例,用于履行数据库操作
}

5. 运转代码生成器

运用以下命令来运转生成器:

flutter packages pub run build_runner build

上面的命令用于在 Flutter 项目中运转代码生成东西以手动构建生成的代码。build命令 只会运转一次生成过程,然后退出。还能够在 Flutter 项目中运用 build_runner 模块的调查模式,以便在开发过程中主动更新生成的代码。假如想要在文件更改时主动运转它,请运用以下命令:

flutter packages pub run build_runner watch

6. 运用生成的代码

要获取数据库的实例,运用生成的 FloorAppDatabase类,它答应拜访数据库生成器。称号由FloorAppDatabase 类,它答应拜访数据库生成器。称号由 Floor 和数据库类名组成。传递给 databaseBuilder() 的字符串将是数据库文件的称号。要初始化数据库,调用 build() 并保证等待成果。

为了检索 PersonDao 实例,只需在数据库实例上调用 persoDao getter 即可。 能够参考下面的代码。

// 运用数据库生成器创立数据库实例,传递数据库文件的称号('app_database.db')
final database = await $FloorAppDatabase.databaseBuilder('app_database.db').build();
// 从数据库实例中获取 PersonDao 实例
final personDao = database.personDao;
// 创立一个 Person 目标
final person = Person(1, 'Frank');
// 将新的 Person 目标刺进到数据库中
await personDao.insertPerson(person);
// 经过 ID 查找数据库中的 Person 记载
final result = await personDao.findPersonById(1);
// 输出查找到的 Person 记载
print('Found Person: $result');