最近Flutter项目有使用sqlite数据库的需求,研究了一下市面上多款插件,最终选择floor这款插件,选择的理由很简单,用起来方便,看起来简洁。
[floor pub地址](floor | Flutter Package (pub.dev))
常规使用
安装
在项目里的pubspec.yaml文件里添加
dependencies:
floor: ^1.2.0
dev_dependencies:
floor_generator: ^1.2.0
build_runner: ^2.1.2
注意区分dependencies和dev_dependencies
创建表
新建一个类,类名上添加@entity注解,如下:
import 'package:floor/floor.dart';
@entity
class User {
@PrimaryKey()
int? id;
String userId;
// 表里字段为custom_name 不可为空
@ColumnInfo(name: 'custom_name', nullable: false)
String name;
User(this.id, this.userId,this.name);
}
@PrimaryKey()表示该字段为自增key字段,或者在@entity中定义,使用大写,例:
@Entity(primaryKeys: ['id', 'name'])
创建Dao类
新建一个类,类名上添加@dao注解,如下:
import 'package:database_demo/database/entity/user_entity.dart';
import 'package:floor/floor.dart';
@dao
abstract class UserDao{
@Query('SELECT * FROM User')
Future<List<User>> findAllUser();
/// 查询语句里的 userId 要和方法参数里的参数名一致
@Query('SELECT * FROM User WHERE userId=:userId')
Future<List<User>> findAllUserById(String userId);
@insert
Future<void> insertUser(User user);
@update
Future<int> updateUser(User user);
@delete
Future<int> deleteUser(User user);
}
创建数据库
创建一个继承FloorDatabase的抽象类。此外,还需要将@Database()注解添加到类上面。确保将创建的实体添加到@Database注释的entities属性中。
为了使生成的代码可以正常使用,还需要添加下面列出的import。
// 必须的包
import 'dart:async';
import 'package:floor/floor.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart' as sqflite;
import 'package:database_demo/database/dao/user_dao.dart';
import 'package:database_demo/database/entity/user_entity.dart';
part 'database.g.dart';
// 执行命令 flutter pub run build_runner build --delete-conflicting-outputs
@Database(version: 1, entities: [User])
abstract class MyDataBase extends FloorDatabase{
UserDao get userDao;
}
创建完成之后终端在当前项目下执行命令生成相关文件:
flutter pub run build_runner build --delete-conflicting-outputs
使用
import 'package:database_demo/database/database.dart';
import 'package:database_demo/database/entity/user_entity.dart';
class DataBaseManager {
static Future<MyDataBase> database() async {
// 关键
// $FloorMyDataBase是生成的类 名称为$Floor+你创建的数据库类名字
final database =
await $FloorMyDataBase.databaseBuilder('app_database.db').build();
return database;
}
/// 查询
static Future<List<User>> queryAllUser() async {
var myDataBase = await database();
return myDataBase.userDao.findAllUser();
}
/// 根据Id查询
static Future<List<User>> queryUserById(String userId) async {
var myDataBase = await database();
return myDataBase.userDao.findAllUserById(userId);
}
/// 插入
static Future<void> insertUser(User user) async {
var myDataBase = await database();
return await myDataBase.userDao.insertUser(user);
}
/// 更新 返回[int] 表示受影响的行数
static Future<int> updateUser(User user) async {
var myDataBase = await database();
return await myDataBase.userDao.updateUser(user);
}
/// 删除 返回[int] 表示受影响的行数
static Future<int> deleteUser(User user) async {
var myDataBase = await database();
return await myDataBase.userDao.deleteUser(user);
}
}
进阶操作
@Query
例:
@Query('SELECT * FROM Person WHERE id = :id')
Future<Person> findPersonById(int id);// 根据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(); // 查找多条数据
@Query('SELECT * FROM Person')
Stream<List<Person>> findAllPersonsAsStream(); // 流式返回
@Query('DELETE FROM Person')
Future<void> deleteAllPersons(); // 无返回值删除所有
@Query('SELECT * FROM Person WHERE id IN (:ids)')
Future<List<Person>> findPersonsWithIds(List<int> ids); // 使用IN子查询
在使用like操作符时,不能在查询本身中定义像%foo%这样的模式匹配参数。正确使用如下:
// dao
@Query('SELECT * FROM Person WHERE name LIKE :name')
Future<List<City>> findPersonsWithNamesLike(String name);
// 使用
final name = '%foo%';
await dao.findPersonsWithNamesLike(name);
@insert
@insert将方法标记为插入方法。当使用大写的@Insert时,可以指定一个冲突策略。否则默认终止插入。如:
/// 冲突默认终止
@insert()
Future<void> insertUser(User user);
/// 大写指定冲突规则
@Insert(onConflict: OnConflictStrategy.replace)
Future<void> insertUser(User user);
/// 可选择的冲突规则
enum OnConflictStrategy {
/// 替换并继续
replace,
/// 回滚
rollback,
/// 中止
abort,
/// 使事务失败
fail,
/// 忽略冲突
ignore,
}
@insert或@Insert可以返回void、int或List<int>的值。
void无返回值int返回插入项目的主键List<int>返回插入项的主键
@Update
@update 将方法标记为更新方法。使用大写的@Update时,可以指定冲突策略。否则,它只是默认中止更新。这些方法可以返回void或int。
void无返回值int受影响的行数
@Delete
@Delete 将方法标记为删除方法。这些方法可以返回void或int。
void无返回值int删除的行数
批量操作示例
@insert
Future<List<int>> insertPersons(List<Person> person);
@update
Future<int> updatePersons(List<Person> person);
@delete
Future<int> deletePersons(List<Person> person);
使用Stream
查询不仅可以在调用时返回一次值,还可以返回连续的查询结果流。返回的流使与发生在数据库表上的更改保持同步。该特性与StreamBuilder非常配合。
这方法返回的是广播流。因此,它可以有多个监听器。
例:
// 定义
@Query('SELECT * FROM Person')
Stream<List<Person>> findAllPersonsAsStream();
// 使用
StreamBuilder<List<Person>>(
stream: dao.findAllPersonsAsStream(),
builder: (BuildContext context, AsyncSnapshot<List<Person>> snapshot) {
// do something with the values here
},
);
Transactions
当您想在事务中执行某些操作时,必须向方法添加@transaction注解。还需要添加async修饰符。这些方法只能返回Future<void>。
例:
@transaction
Future<void> replacePersons(List<Person> persons) async {
await deleteAllPersons();
await insertPersons(persons);
}
使用外键
向引用实体的@Entity注释添加一个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);
}
索引
向@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);
}
忽略字段
默认情况下,实体的hashCode属性和所有静态字段都会被忽略,因此会从库的映射中排除。如果需要忽略其他字段,则应该使用@ignore注解,如下:
class Person {
@primaryKey
final int id;
final String name;
@ignore
String nickname;
Person(this.id, this.name);
}
迁移
无论何时对实体进行更改,都需要迁移旧数据。首先,更新的实体。接下来,增加数据库版本。定义一个Migration,它指定startVersion、endVersion和一个执行SQL来迁移数据的函数。最后,在获得的数据库构建器上使用addMigrations()来添加迁移。不要忘记再次执行代码生成命令,以创建用于处理新实体的代码。
例:
// 新增nikeName字段
@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);
}
// 提升数据库版本
@Database(version: 2)
abstract class AppDatabase extends FloorDatabase {
PersonDao get personDao;
}
// 创建一个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();
关键事件回调
final callback = Callback(
onCreate: (database, version) { /* 已创建数据库 */ },
onOpen: (database) { /* 数据库已打开*/ },
onUpgrade: (database, startVersion, endVersion) { /* 数据库已升级 */ },
);
final database = await $FloorAppDatabase
.databaseBuilder('app_database.db')
.addCallback(callback) // 关键
.build();