目录
📂 1. 简介
1.1 Drift vs sqflite
1.2 Drift vs Hive / Sembast vs SharedPreferences
2. 🔱 核心架构设计
3. 💠 使用步骤
3.1 添加依赖
3.2 插入数据
3.3 查询数据
3.4 Drift 核心操作配套表
1. 为什么有“终结方法”?
2. 响应式编程
3.5 运行代码生成
4. ⚛️ 工程化封装
4.1 Bean 层:定义表结构
4.2 DAO 层:封装业务逻辑
4.3 DB 层:数据库引擎与迁移
4.4 Manager 层:单例管理
5. ✅ 小结
📂 1. 简介
在 Flutter 开发中,选择一个合适的本地数据库方案至关重要。从早期的 sqflite 到 NoSQL 的 Hive,再到如今功能强大的 Drift,开发者们一直在追求更高效、更安全的持久化方案。
本文将带大家深度实战Drift(原 Moor),通过Bean -> DB -> DAO -> Manager的分层设计,构建一套可直接用于生产环境的数据库模块。
在开始代码之前,我们先通过两张对比表看看 Drift 的江湖地位。
1.1 Drift vs sqflite
特性 | Drift | sqflite |
类型安全 | 编译时检查,代码自动生成实体类,减少运行时错误 | 手动解析 Map,字符串硬编码,易出错 |
查询构建 | 强大的 Fluent API,无需手写 SQL | 必须手写 SQL 字符串,复杂查询维护难 |
迁移管理 | 内置自动迁移与版本策略,简单清晰 | 手动编写升级脚本,维护成本极高 |
开发效率 | 自动生成 CRUD,效率翻倍 | 繁琐的样板代码,开发慢 |
Drift适合复杂项目,提供了类型安全、高效查询和自动迁移支持,能显著提高开发效率;
sqflite更适合简单的数据库需求,但对于复杂数据库操作和迁移,维护成本较高。
1.2 Drift vs Hive / Sembast vs SharedPreferences
特性 | Drift | Hive / Sembast | SharedPreferences |
数据模型 | 关系型 (RDBMS),支持表、外键 | NoSQL 键值对 Hive或文档存储 Sembast,适合轻量存储 | 键值对存储,适合配置和简单数据 |
响应式 | 原生支持Stream 监听 | 支持监听,但复杂联动较弱 | 不支持查询与监听 |
适用场景 | 中大型、复杂业务逻辑项目 | 轻量缓存、小型项目 | 配置项、用户偏好设置 |
Drift:Drift 提供了类型安全、自动迁移和响应式流支持,是中大型 Flutter 应用的首选。
2. 🔱 核心架构设计
为了保证模块的可维护性,我们采用分层架构:
Bean (Table):定义表结构;
DB (AppDatabase):数据库配置、连接与迁移逻辑;
DAO (Data Access Object):隔离业务 SQL 逻辑;
Manager (DBManager):全局单例,屏蔽初始化细节。
3. 💠 使用步骤
3.1 添加依赖
# pubspec.yaml dependencies: # 提供了在设备上查找常用文件目录的方法,如获取应用的文档目录、临时目录等。 path_provider: ^2.1.3 # # SQLite 数据库插件,提供了对 SQLite 数据库的访问和操作功能。 # sqflite: ^2.4.2 # Drift 是一个强大的 Flutter 和 Dart 的数据库库,简化了与 SQLite 数据库的交互。 drift: ^2.16.0 # SQLite 的 Flutter 插件,提供了 SQLite 数据库的本地支持。 sqlite3_flutter_libs: ^0.5.0 # 提供了对文件和目录路径进行操作的功能,如拼接路径、获取文件名、获取父目录等。 path: ^1.9.1 # ⚠️ 必须添加以下开发依赖,否则无法生成 .g.dart 代码 dev_dependencies: # Drift 的代码生成器,负责解析你的 Table 定义 drift_dev: ^2.16.0 # 运行代码生成的通用工具 build_runner: ^2.4.93.2 插入数据
void addNote() async { await DBManager.noteDao.insertNote( NotesCompanion.insert( title: '会议记录', content: '讨论 Flutter 数据库方案', time: Value(DateTime.now().millisecondsSinceEpoch), ), ); }3.3 查询数据
DBManager.noteDao.getAllNotes().then((notes) { for (var note in notes) { logD(tag: "DB Note:", note.toString()); } });3.4 Drift 核心操作配套表
操作类型 | 启动方法(开始) | 常见中间方法(加工) | 终结方法(发令枪) | 返回值类型 |
查询 |
|
|
|
|
查询 |
|
|
|
|
查询 | select(table) |
|
|
|
插入 |
| 无 |
|
|
删除 |
|
|
|
|
更新 |
|
|
|
|
更新 |
| 无 |
|
|
1. 为什么有“终结方法”?
Drift 的查询逻辑遵循:启动 -> 加工 -> 发令枪。
启动:select() / into() / delete()。
加工:where() / orderBy()。
发令枪:.get() (一次性)、.watch() (流)、.go() (执行)。 只有扣动“发令枪”,SQL 才会真正执行。
2. 响应式编程
通过 watchAllNotes() 配合 Flutter 的 StreamBuilder,你可以实现数据变动时 UI 的无感刷新。这在开发笔记应用、聊天列表等场景下非常高效。
3.5 运行代码生成
在终端执行以下命令,让 build_runner 为你生成那几千行枯燥的样板代码:
flutter pub run build_runner build --delete-conflicting-outputsTips: 建议将生成的 .g.dart 文件提交到 Git,这样同事拉下代码后无需运行 build 命令即可编译。
4. ⚛️ 工程化封装
4.1 Bean 层:定义表结构
Drift 通过 Dart 类来定义 SQL 表。注意 () 语法,它实际上是 Drift 的构建器模式。
/// /// Description: 记录表 /// CreateDate: 2026/1/4 17:33 /// Author: agg /// class Notes extends Table { IntColumn get id => integer().autoIncrement()(); // 主键,自增 IntColumn get time => integer().withDefault(Constant(-1))(); // 记录生成时间 TextColumn get title => text().withLength(min: 1, max: 255)(); // 标题 TextColumn get content => text()(); // 内容 }4.2 DAO 层:封装业务逻辑
DAO 负责具体的增删改查,利用 Drift 的流式 API,代码极度丝滑。
part 'note_dao.g.dart'; /// /// Description: 记录表 数据访问对象 /// CreateDate: 2026/1/4 17:36 /// Author: agg /// @DriftAccessor(tables: [Notes]) class NoteDao extends DatabaseAccessor<AppDatabase> with _$NoteDaoMixin { NoteDao(super.db); // 插入 Future<int> insertNote(NotesCompanion note) => into(notes).insert(note); // 查询全部 Future<List<Note>> getAllNotes() => select(notes).get(); // 监听数据流 (用于 UI 实时刷新) Stream<List<Note>> watchAllNotes() => select(notes).watch(); // 更新 Future<bool> updateNote(Note note) => update(notes).replace(note); // 删除 Future<int> deleteNote(int id) => (delete(notes)..where((t) => t.id.equals(id))).go(); }4.3 DB 层:数据库引擎与迁移
这里包含了底层的连接配置。我们特别针对 Android 的目录规范做了优化,将 .sqlite 文件放在标准的 databases 目录下。
part 'app_database.g.dart'; // Drift 自动生成部分 /// /// Description: 应用数据库 /// CreateDate: 2026/1/4 17:39 /// Author: agg /// @DriftDatabase(tables: [Notes], daos: [NoteDao]) class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override int get schemaVersion => 1; // 生产环境升级表结构时需自增 // 这里的迁移策略可根据需求扩展,如 onCreate, onUpgrade @override MigrationStrategy get migration => MigrationStrategy( // 当数据库文件【第一次】在手机上创建时执行 onCreate: (m) async { // m 是间接操作数据库的工具 // createAll() 会扫描 @DriftDatabase 里的 tables 列表,自动执行所有的 CREATE TABLE 语句 await m.createAll(); }, // 当 schemaVersion 增加时(比如从 1 变 2),会触发这个方法 onUpgrade: (m, from, to) async { if (from < 2) { // 执行升级逻辑,比如给 Notes 表加个字段 // await m.addColumn(notes, notes.type); } }, ); } LazyDatabase _openConnection() { return LazyDatabase(() async { String path = ""; if (Platform.isAndroid) { // 获取 /data/data/包名/ 根目录 final dbFolder = await getApplicationSupportDirectory(); // 向上级找并进入 databases 文件夹 path = p.join(dbFolder.parent.path, 'databases', 'app_database.sqlite'); } else { // iOS 等其他平台保持原样 final dbFolder = await getApplicationDocumentsDirectory(); path = p.join(dbFolder.path, 'app_database.sqlite'); } return NativeDatabase( File(path), // 将 logStatements 设为 true:可以看到生成的 SQL 语句,生产环境建议关闭 SQL 日志打印 logStatements: true, // 默认情况下,SQLite 在写入时会锁定数据库。开启 WAL (Write-Ahead Logging) 模式可以实现“读写并发”,显著提升性能 setup: (rawDb) { // 开启预写日志模式,提升并发性能 rawDb.execute('PRAGMA journal_mode = WAL;'); // 开启外键约束 rawDb.execute('PRAGMA foreign_keys = ON;'); }, ); }); }4.4 Manager 层:单例管理
对外提供统一的静态入口。
/// /// Description: 数据库管理类,运行前可选执行Runner初始化:flutter pub run build_runner build --delete-conflicting-outputs /// CreateDate: 2026/1/4 17:55 /// Author: agg /// class DBManager { // 1. 私有静态实例 static final DBManager _instance = DBManager._internal(); // 2. 数据库实例 late final AppDatabase _db; // 3. 工厂构造函数返回单例 factory DBManager() => _instance; // 4. 私有构造函数完成初始化 DBManager._internal() { _db = AppDatabase(); } // 5. 静态快捷访问方式 // 使用 instance 获取能确保单例已创建,逻辑更清晰 static NoteDao get noteDao => _instance._db.noteDao; }5. ✅ 小结
Drift 的设计哲学是将 SQL 的强大与 Dart 的安全完美结合。通过本篇的模块化封装,我们不仅隔离了底层复杂度,还为后续的业务扩展打下了坚实基础。
另外,由于本人能力有限,如有错误,敬请批评指正,谢谢。