美文网首页Flutter
floor flutter数据库操作库

floor flutter数据库操作库

作者: 缘焕 | 来源:发表于2020-08-08 14:11 被阅读0次

    floor flutter数据库操作库

    github地址

    Floor为您的Flutter应用程序提供了一个简洁的SQLite抽象,其灵感来自于Room持久性库,它提供了内存中对象和数据库行之间的自动映射,同时仍然使用SQL提供对数据库的完全控制。因此,有必要了解SQL和SQLite,以便充分挖掘Floor的潜力。

    类型安全

    响应式

    轻量级

    SQL为中心的

    没有隐藏的魔法 no hidden magic

    没有隐藏成本 no hidden costs

    支持iOS, Android, Linux, macOS, Winsdows

    这个库正在迈向它的第一个稳定发行版!在集成了类型转换器和可嵌入对象之后,API表面在1.0之后才会改变。

    快速入门

    1.添加依赖

    dependencies:
       flutter:
         sdk: flutter
       floor: ^0.14.0
    
     dev_dependencies:
       floor_generator: ^0.14.0
       build_runner: ^1.7.3
    

    2.创建一个实体

    它将表示一个数据库表以及业务对象的框架,

    @entity 标记这一个类是对应一个数据库表。

    @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);
     }
    

    3.创建一个DAO(数据访问对象)

    该组件负责管理对底层SQLite数据库的访问,抽象类包含查询数据库的方法签名,这些方法签名必须返回一个FutureStream

    可以通过向方法中添加@Query注释来定义查询,SQL语句必须添加到括号中,该方法必须返回您正在查询的实体的FutureStream

    @insert将方法标记为插入方法。

    // dao/person_dao.dart
    
     import 'package:floor/floor.dart';
    
     @dao
     abstract class PersonDao {
       @Query('SELECT * FROM Person')
       Future<List<Person>> findAllPersons();
    
       @Query('SELECT * FROM Person WHERE id = :id')
       Stream<Person> findPersonById(int id);
    
       @insert
       Future<void> insertPerson(Person person);
     }
    

    4.创建数据库

    它必须是一个继承FloorDatabase的抽象类,此外,需要将@Database()添加到类的签名中,确保将2.创建一个实体这一步创建的实体添加到@Databaseentities注解属性中

    为了使生成的代码工作,还需要导入所需要的类。

     // database.dart
    
     // required package imports
     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'; // the generated code will be there
    
     @Database(version: 1, entities: [Person])
     abstract class AppDatabase extends FloorDatabase {
       PersonDao get personDao;
     }
    

    注意:

    1.确保添加部分‘database.g.dart’;在这个文件的导入下面,需要注意的是,‘database’必须与数据库定义的文件名进行交换。在本例中,文件名为database.dart,所以part 'database.g.dart';

    2.运行以下命令 flutter packages pub run build_runner build,如果需要在文件变动时,自动运行命令使用flutter packages pub run build_runner watch

    5.使用生成的代码

    为了获得数据库的实例,使用生成的$FloorAppDatabase类,它允许访问数据库构建器。该名称由$Floor和数据库类名组成

    传递给databaseBuilder()的字符串将是数据库文件名。

    要初始化数据库,请调用build()并使用await确保结果。

    为了检索PersonDao实例,在数据库实例上调用persoDao getter就足够了。它的函数可以如下面的代码片段所示。

     final database = await $FloorAppDatabase.databaseBuilder('app_database.db').build();
     final personDao = database.personDao;
    
     final person = Person(1, 'Frank');
     await personDao.insertPerson(person);
    
     final result = await personDao.findPersonById(1);
    

    要了解更多示例,请查看examplefloor_test目录。

    架构

    用于存储和访问数据的组件有 Entity, Data Access Object (DAO) and Database.

    首先,Entity表示一个持久类,代表是一个数据库表

    Data Access Object (DAO) 管理对实体的访问,并负责内存中对象和表行之间的映射

    最后Database是底层SQLite数据库的中央访问点,它操作dao,并负责初始化数据库及其模式, Room 是这个构图的灵感来源,因为它允许对组件的职责进行清晰的分离

    该图显示了实体、DAO和数据库之间的关系。

    floor-architecture.png

    实体对象

    实体是一个持久类,Floor自动创建内存中对象和数据库表行之间的映射,通过向实体注解中添加可选值,可以向底层提供定制元数据。
    它还有tableName的附加属性,可以为特定实体使用自定义名,而不是类名。

    外键允许向实体添加外键。关于如何使用这些的更多信息可以在外键部分中找到,

    也支持索引。它们可以通过向实体的索引值添加一个索引来使用。有关这些指标的进一步资料,请参阅指数部分。

    @PrimaryKey将类的属性标记为主键列,此属性的类型必须是int,当启用自动生成时,SQLite可以自动生成该值,有关主键特别是复合主键的详细信息,请参考主键部分。

    @ColumnInfo 启用单表列的自定义映射,使用注释,可以为列提供自定义名称,并定义列是否能够存储空值

    Limitations

    Floor自动使用entity类中定义的第一个构造函数从数据库行创建内存中的对象。

    需要有一个构造函数。

    @Entity(tableName: 'person')
    class Person {
      @PrimaryKey(autoGenerate: true)
      final int id;
    
      @ColumnInfo(name: 'custom_name', nullable: false)
      final String name;
    
      Person(this.id, this.name);
    }
    

    支持的类型

    地板实体可以保存以下Dart类型的值,这些Dart类型映射到它们对应的SQLite类型,反之亦然

    • int - INTEGER

    • double - REAL

    • String - TEXT

    • bool - INTEGER (0 = false, 1 = true)

    • Uint8List - BLOB

    Primary Keys

    每当需要一个复合主键时(例如n-m关系),设置复合主键的语法与前面提到的设置主键的方法不同。使用的不是@PrimaryKey注释字段,而是@Entity注释的primaryKey属性。它接受组成复合主键的列名列表

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

    外键

    ForeignKeys用于向实体添加一个外键列表

    childColumns 注解定义当前实体的列

    parentColumns 定义父实体的列

    在为onUpdate和onDelete属性定义外键操作之后,可以触发它们

    @Entity(
      tableName: 'dog',
      foreignKeys: [
        ForeignKey(
          childColumns: ['owner_id'],
          parentColumns: ['id'],
          entity: Person,
        )
      ],
    )
    class Dog {
      @PrimaryKey()
      final int id;
    
      final String name;
    
      @ColumnInfo(name: 'owner_id')
      final int ownerId;
    
      Dog(this.id, this.name, this.ownerId);
    }
    

    索引

    索引有助于加快查询、联接和分组操作.有关SQLite索引的更多信息,请参阅官方文档

    要使用floor创建索引,请向@Entity注解添加索引列表。下面的示例展示了如何在实体的custom_name列上创建索引

    而且,索引可以通过使用其name属性来命名。若要将索引设置为唯一,请使用unique属性

    @Entity(tableName: 'person', indices: [Index(value: ['custom_name'])])
    class Person {
      @primaryKey
      final int id;
    
      @ColumnInfo(name: 'custom_name', nullable: false)
      final String name;
    
      Person(this.id, this.name);
    }
    

    忽略字段

    默认情况下,实体的getter、setter和所有静态字段都被忽略,因此被排除在库的映射之外。如果进一步的字段应该被忽略,应该使用和应用@ignore注释,如下面的代码片段所示

    class Person {
      @primaryKey
      final int id;
    
      final String name;
    
      @ignore
      String nickname;
    
      // ignored by default
      String get combinedName => "$name ($nickname)";
    
      Person(this.id, this.name);
    }
    

    继承

    与dao一样,实体(和数据库视图)可以从一个公共基类继承并使用它们的字段,实体只需要扩展基类,这个构造将被视为基类中的所有字段都是实体的一部分,这意味着数据库表将拥有该实体和基类的所有列

    基类不必为类提供单独的注解,它的字段可以像普通的实体列一样进行注释。外键和索引必须在实体中声明,不能在基类中定义

    class BaseObject {
      @PrimaryKey()
      final int id;
    
      @ColumnInfo(name: 'create_time', nullable: false)
      final String createTime;
    
      @ColumnInfo(name: 'update_time')
      final String updateTime;
    
      BaseObject(
        this.id,
        this.updateTime, {
        String createTime,
      }) : this.createTime = createTime ?? DateTime.now().toString();
    
      @override
      List<Object> get props => [];
    }
    
    @Entity(tableName: 'comments')
    class Comment extends BaseObject {
      final String author;
    
      final String content;
    
      Comment(this.author,
          {int id, this.content = '', String createTime, String updateTime})
          : super(id, updateTime, createTime: createTime);
    }
    

    数据库视图

    如果您想定义静态选择语句,它返回的类型与您的实体不同,您最好的选择是使用@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 getters
    }
    

    然后,您可以像实体一样通过DAO函数查询视图。

    DatabaseViews可以从基类继承公共字段,就像在实体中一样。

    局限性

    现在可以从查询数据库视图的DAO方法返回Stream对象,

    但是它会整个数据库中的任何@update, @insert, @delete事件中触发,这会对运行时造成很大的负担,只在你知道你在做什么时添加它!这主要是由于检测数据库视图中涉及哪些实体的复杂性。

    数据访问对象

    这些组件负责管理对底层SQLite数据库的访问,并被定义为具有方法签名和查询语句的抽象类。DAO类可以通过在使用mixin的同时实现和扩展类来使用继承的方法。

    @dao
    abstract class PersonDao {
      @Query('SELECT * FROM Person')
      Future<List<Person>> findAllPersons();
    
      @Query('SELECT * FROM Person WHERE id = :id')
      Stream<Person> findPersonById(int id);
    
      @insert
      Future<void> insertPerson(Person person);
    }
    
    查询

    通过向方法签名添加带有括号中的查询的@Query()注释,方法签名转换为查询方法,请耐心等待SQL语句的正确性。在生成代码时,只对它们进行了部分验证。这些查询必须返回实体或空的Future或Stream。当您想删除表的完整内容时,返回Future<void>很方便,下面是一些查询方法示例。

    @Query('SELECT * FROM Person WHERE id = :id')
    Future<Person> findPersonById(int id);
    
    @Query('SELECT * FROM Person WHERE id = :id AND name = :name')
    Future<Person> findPersonByIdAndName(int id, String name);
    
    @Query('SELECT * FROM Person')
    Future<List<Person>> findAllPersons(); // select multiple items
    
    @Query('SELECT * FROM Person')
    Stream<List<Person>> findAllPersonsAsStream(); // stream return
    
    @Query('DELETE FROM Person')
    Future<void> deleteAllPersons(); // query without returning an entity
    
    @Query('SELECT * FROM Person WHERE id IN (:ids)')
    Future<List<Person>> findPersonsWithIds(List<int> ids); 
    

    在使用SQLite的LIKE操作符时,查询参数必须由方法的输入提供,不能在查询本身中定义像%foo%这样的模式匹配参数。

    // dao
    @Query('SELECT * FROM Person WHERE name LIKE :name')
    Future<List<Person>> findPersonsWithNamesLike(String name);
    
    // usage
    final name = '%foo%';
    await dao.findPersonsWithNamesLike(name);
    

    数据更改

    使用@insert、@update和@delete注解插入和更改持久数据,所有这些方法都接受单个或多个实体实例。

    @insert将方法标记为插入方法,当使用大写的@Insert时,您可以指定一个冲突策略,否则,它将默认终止插入。

    这些方法可以返回void、int或List<int>的Future

    • void return nothing

    • int return primary key of inserted item

    • List<int> return primary keys of inserted items

    @update将方法标记为更新方法,当使用大写的@Update时,您可以指定一个冲突策略,否则,它将默认中止更新,这些方法可以返回void或int的Future

    • void return nothing

    • int return number of changed rows

      @delete将一个方法标记为删除方法,这些方法可以返回void或int的Future

    • void return nothing

    • int return number of deleted rows

    // examples of changing multiple items with return
    
    @insert
    Future<List<int>> insertPersons(List<Person> person);
    
    @update
    Future<int> updatePersons(List<Person> person);
    
    @delete
    Future<int> deletePersons(List<Person> person);
    

    数据流

    返回的流使您与数据库表中发生的更改保持同步,返回的流使您与数据库表中发生的更改保持同步。这个特性在StreamBuilder小部件上很好地发挥作用,它接受一个值流,并在出现新的发射时重新构建自己。

    这些方法返回广播流,因此可以有多个侦听器。

    // definition
    @Query('SELECT * FROM Person')
    Stream<List<Person>> findAllPersonsAsStream();
    
    // usage
    StreamBuilder<List<Person>>(
      stream: dao.findAllPersonsAsStream(),
      builder: (BuildContext context, AsyncSnapshot<List<Person>> snapshot) {
        // do something with the values here
      },
    );
    

    局限性

    只有注解了@insert、@update和@delete的方法才会触发流排放。通过使用@Query()注释插入数据则不需要

    现在,如果函数查询数据库视图,则可以返回一个流。但是它会触发整个数据库中的任何@update、@insert、@delete事件,这会对运行时造成相当大的负担,如果你知道你在做什么,请添加它!这主要是由于检测数据库视图中涉及哪些实体的复杂性

    当没有查询结果时,返回单个项流(如stream <Person>)的函数不会发出

    事务

    每当您想在事务中执行某些操作时,您就必须向方法中添加@transaction注解。

    @transaction
    Future<void> replacePersons(List<Person> persons) async {
      await deleteAllPersons();
      await insertPersons(persons);
    }
    

    继承

    数据访问对象类支持继承,如下所示,继承级别没有限制,因此每个抽象父对象可以有另一个抽象父对象。请记住,只有抽象类允许没有实现主体的方法签名,因此确保将要继承的方法定位在一个抽象类中,并使用DAO扩展这个类

    @dao
    abstract class PersonDao extends AbstractDao<Person> {
      @Query('SELECT * FROM Person WHERE id = :id')
      Future<Person> findPersonById(int id);
    }
    
    abstract class AbstractDao<T> {
      @insert
      Future<void> insertItem(T item);
    }
    
    // usage
    final person = Person(1, 'Simon');
    await personDao.insertItem(person);
    
    final result = await personDao.findPersonById(1);
    

    Migrations

    在对实体进行更改时,还需要迁移旧数据。首先,更新您的实体。接下来,增加数据库版本。定义一个迁移,指定startVersion、endVersion和一个执行SQL来迁移数据的函数,最后,在获得的数据库构建器上使用addMigrations()来添加迁移,不要忘记再次触发代码生成器,以创建用于处理新实体的代码。

    // update entity with new 'nickname' field
    @Entity(tableName: 'person')
    class Person {
      @PrimaryKey(autoGenerate: true)
      final int id;
    
      @ColumnInfo(name: 'custom_name', nullable: false)
      final String name;
    
      final String nickname;
    
      Person(this.id, this.name, this.nickname);
    }
    
    // bump up database version
    @Database(version: 2)
    abstract class AppDatabase extends FloorDatabase {
      PersonDao get personDao;
    }
    
    // create migration
    final migration1to2 = Migration(1, 2, (database) async {
      await database.execute('ALTER TABLE person ADD COLUMN nickname TEXT');
    });
    
    final database = await $FloorAppDatabase
        .databaseBuilder('app_database.db')
        .addMigrations([migration1to2])
        .build();
    

    内存数据库

    要实例化内存中的数据库,请使用生成的$FloorAppDatabase类的静态inMemoryDatabaseBuilder()方法,而不是databaseBuilder()

    final database = await $FloorAppDatabase.inMemoryDatabaseBuilder().build();
    

    平台支持

    Floor支持iOS、Android、Linux、macOS和Windows,

    iOS和Android上的SQLite数据库访问是由sqflite提供的,而Linux、macOS和Windows使用sqflite的ffi实现。

    目前还没有对web的Flutter的支持。

    相关文章

      网友评论

        本文标题:floor flutter数据库操作库

        本文链接:https://www.haomeiwen.com/subject/uplhdktx.html