334  
查询码:00000170
Flutter 数据库ORM框架floor使用详解
来源:https://blog.csdn.net/mqdxiaoxiao/article/details/102858691
作者: 朱凡 于 2021年01月29日 发布在分类 / FM组 / FM_App 下,并于 2021年01月29日 编辑
flutter database person 数据 使用 entity 数据库 框架 future 一个

Flutter 数据库ORM框架floor使用详解

选型

数据库ORM框架可以提高开发效率,不用写一大堆的sql

网上搜了下,资料不多,官方也没有提供,第三方框架有但是在GitHub上的star数量都不高,以下是几个star数量比较靠前的:

  • flutter_orm_plugin:GitHub上的demo及文档

  • jaguar_query_sqflite

  • sqfentity

  • floor

对比了下,最后㐊选择了floor,因为它的API调用看起来比较舒服,和Android官方的Room框架比较类似。

使用方法

使用方法比较简单,直接看floor或者GitHub上的的文档写得很清晰,如果之前有用过Android的其他ORM框架的话,入手很快,直接看官方文档里的示例代码基本知道怎么用了。

这里直接copy官网上的文档,方便自己也方便大家查阅,基于版本0.9.0,如果有最新版本,可以到pub.dev或者GitHub查看


Table of contents #

  1. How to use this library
  2. Architecture
  3. Querying
  4. Persisting Data Changes
  5. Streams
  6. Transactions
  7. Entities
  8. Foreign Keys
  9. Primary Keys
  10. Indices
  11. Migrations
  12. In-Memory Database
  13. Callback
  14. Examples
  15. Naming
  16. Bugs and Feedback
  17. License

How to use this library #

  1. Add the runtime dependencyflooras well as the generatorfloor_generatorto yourpubspec.yaml. The third dependency isbuild_runnerwhich has to be included as a dev dependency just like the generator.

    • floorholds all the code you are going to use in your application.

    • floor_generatorincludes the code for generating the database classes.

    • build_runnerenables a concrete way of generating source code files.

    dependencies:
      flutter:
       sdk: flutter
      floor: ^0.9.0
    
     dev_dependencies:
      floor_generator: ^0.9.0
      build_runner: ^1.7.1
  2. Creating an Entity

    It will represent a database table as well as the scaffold of your business object.@entitymarks the class as a persistent class. It's required to add a primary key to your table. You can do so by adding the@primaryKeyannotation to anintproperty. There is no restriction on where you put the file containing the entity.

    // entity/person.dart
    
     import 'package:floor/floor.dart';
    
     @entity
     class Person {
      @primaryKey
      final int id;
    
      final String name;
    
      Person(this.id, this.name);
     }
  3. Creating a DAO

    This component is responsible for managing access to the underlying SQLite database. The abstract class contains the method signatures for querying the database which have to return aFuture.

    • You can define queries by adding the@Queryannotation to a method. The SQL statement has to get added in parenthesis. The method must return aFutureof theEntityyou're querying for.

    • @insertmarks a method as an insertion method.

    // 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')
      Future<Person> findPersonById(int id);
    
      @insert
      Future<void> insertPerson(Person person);
     }
  4. Creating the Database

    It has to be an abstract class which extendsFloorDatabase. Furthermore, it's required to add@Database()to the signature of the class. Make sure to add the created entity to theentitiesattribute of the@Databaseannotation.

    In order to make the generated code work, it's required to also add the listed imports.

    // database.dart
    
     // required package imports
     import 'dart:async';
     import 'package:floor/floor.dart';
     import 'package:path/path.dart';
     import 'package:sqflite/sqflite.dart' as sqflite;  
    
     import 'dao/person_dao.dart';
     import 'model/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;
     }
  5. Make sure to addpart 'database.g.dart';beneath the imports of this file. It's important to note, that 'database' has to get exchanged with the name of the file the entity and database is defined in. In this case, the file is nameddatabase.dart.

  6. Run the generator withflutter packages pub run build_runner build. To automatically run it, whenever a file changes, useflutter packages pub run build_runner watch.

  7. Use the generated code. For obtaining an instance of the database, use the generated$FloorAppDatabaseclass, which allows access to a database builder. The name is composited from$Floorand the database class name. The string passed todatabaseBuilder()will be the database file name. For initializing the database, callbuild().

    final database = await $FloorAppDatabase.databaseBuilder('app_database.db').build();
    
     final person = await database.findPersonById(1);
     await database.insertPerson(person);

For further examples take a look at the example and floor_test directories.

Architecture #

The components for storing and accessing data are Entity, Data Access Object (DAO) and Database.

The first, Entity, represents a persistent class and thus a database table. DAOs manage the access to Entities and take care of the mapping between in-memory objects and table rows. Lastly, Database, is the central access point to the underlying SQLite database. It holds the DAOs and, beyond that, takes care of initializing the database and its schema. Room serves as the source of inspiration for this composition, because it allows creating a clean separation of the component's responsibilities.

The figure shows the relationship between Entity, DAO and Database.

Floor Architecture

Querying #

Method signatures turn into query methods by adding the@Query()annotation with the query in parenthesis to them. Be patient about the correctness of your SQL statements. They are only partly validated while generating the code. These queries have to return either aFutureor aStreamof an entity orvoid. ReturningFuture<void>comes in handy whenever you want to delete the full content of a table, for instance. Some query method examples can be seen in the following.

@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); // query with IN clause

Query arguments, when using SQLite'sLIKEoperator, have to be supplied by the input of a method. It's not possible to define a pattern matching argument like%foo%in the query itself.

// dao
@Query('SELECT * FROM Person WHERE name LIKE :name')
Future<List<City>> findPersonsWithNamesLike(String name);

// usage
final name = '%foo%';
await dao.findPersonsWithNamesLike(name);

Persisting Data Changes #

Use the@insert,@updateand@deleteannotations for inserting and changing persistent data. All these methods accept single or multiple entity instances.

  • Insert

    @insertmarks a method as an insertion method. When using the capitalized@Insertyou can specify a conflict strategy. Else it just defaults to aborting the insert. These methods can return aFutureof eithervoid,intorList<int>.

    • voidreturn nothing
    • intreturn primary key of inserted item
    • List<int>return primary keys of inserted items
  • Update

    @updatemarks a method as an update method. When using the capitalized@Updateyou can specify a conflict strategy. Else it just defaults to aborting the update. These methods can return aFutureof eithervoidorint.

    • voidreturn nothing
    • intreturn number of changed rows
  • Delete

    @deletemarks a method as a deletion method. These methods can return aFutureof eithervoidorint.

    • voidreturn nothing
    • intreturn 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);

Streams #

As already mentioned, queries can not only return a value once when called but also a continuous stream of query results. The returned stream keeps you in sync with the changes happening to the database table. This feature plays really well with theStreamBuilderwidget.

These methods return a broadcast stream. Thus, it can have multiple listeners.

// 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
 },
);

Transactions #

Whenever you want to perform some operations in a transaction you have to add the@transactionannotation to the method. It's also required to add theasyncmodifier. These methods can only returnFuture<void>.

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

Entities #

An entity is a persistent class. Floor automatically creates the mappings between the in-memory objects and database table rows. It's possible to supply custom metadata to Floor by adding optional values to theEntityannotation. It has the additional attribute oftableNamewhich opens up the possibility to use a custom name for that specific entity instead of using the class name.foreignKeysallows adding foreign keys to the entity. More information on how to use these can be found in the Foreign Keys section. Indices are supported as well. They can be used by adding anIndexto theindicesvalue of the entity. For further information of these, please refer to the Indices section.

@PrimaryKeymarks property of a class as the primary key column. This property has to be of type int. The value can be automatically generated by SQLite whenautoGenerateis enabled. For more information about primary keys and especially compound primary keys, refer to the Primary Keys section.

@ColumnInfoenables custom mapping of single table columns. With the annotation, it's possible to give columns a custom name and define if the column is able to storenull.

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

Primary Keys #

Whenever a compound primary key is required (e.g. n-m relationships), the syntax for setting the keys differs from the previously mentioned way of setting primary keys. Instead of annotating a field with@PrimaryKey, the@Entityannotation'sprimaryKeyattribute is used. It accepts a list of column names that make up the compound primary key.

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

 final String name;

 Person(this.id, this.name);
}

Foreign Keys #

Add a list ofForeignKeys to theEntityannotation of the referencing entity.childColumnsdefine the columns of the current entity, whereasparentColumnsdefine the columns of the parent entity. Foreign key actions can get triggered after defining them for theonUpdateandonDeleteproperties.

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

Indices #

Indices help speeding up query, join and grouping operations. For more information on SQLite indices please refer to the official documentation. To create an index with floor, add a list of indices to the@Entityannotation. The example below shows how to create an index on thecustom_namecolumn of the entity.

The index, moreover, can be named by using itsnameattribute. To set an index to be unique, use theuniqueattribute.

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

Migrations #

Whenever are doing changes to your entities, you're required to also migrate the old data.

First, update your entity. Next, Increase the database version. Define aMigrationwhich specifies astartVersion, anendVersionand a function that executes SQL to migrate the data. At last, useaddMigrations()on the obtained database builder to add migrations. Don't forget to trigger the code generator again, to create the code for handling the new entity.

// 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) {
 database.execute('ALTER TABLE person ADD COLUMN nickname TEXT');
});

final database = await $FloorAppDatabase
  .databaseBuilder('app_database.db')
  .addMigrations([migration1to2])
  .build();

In-Memory Database #

To instantiate an in-memory database, use the staticinMemoryDatabaseBuilder()method of the generated$FloorAppDatabaseclass instead ofdatabaseBuilder().

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

Callback #

In order to hook into Floor's database initialization process,Callbackshould be used. It allows the invocation of three separate callbacks which are triggered when the database has been

  • initialized for the first time (onCreate).
  • opened (onOpen).
  • upgraded (onUpgrade).

Each callback is optional.

Their usage can be seen in the following snippet.

final callback = Callback(
  onCreate: (database, version) { /* database has been created */ },
  onOpen: (database) { /* database has been opened */},
  onUpgrade: (database, startVersion, endVersion) { /* database has been upgraded */ },
);

final database = await $FloorAppDatabase
  .databaseBuilder('app_database.db')
  .addCallback(callback)
  .build();

Examples #

For further examples take a look at the example and floor_test directories.

Naming #

Floor - the bottom layer of a Room.



 推荐知识

 历史版本

修改日期 修改人 备注
2021-01-29 18:46:41[当前版本] 朱凡 创建版本

 附件

附件类型

GIFGIF

知识分享平台 -V 4.8.7 -wcp