Flutter 数据库模块之 Draft 设计

目录

[📂 1. 简介](#📂 1. 简介)

[1.1 Drift vs sqflite](#1.1 Drift vs sqflite)

[1.2 Drift vs Hive / Sembast vs SharedPreferences](#1.2 Drift vs Hive / Sembast vs SharedPreferences)

[2. 🔱 核心架构设计](#2. 🔱 核心架构设计)

[3. 💠 使用步骤](#3. 💠 使用步骤)

[3.1 添加依赖](#3.1 添加依赖)

[3.2 插入数据](#3.2 插入数据)

[3.3 查询数据](#3.3 查询数据)

[3.4 Drift 核心操作配套表](#3.4 Drift 核心操作配套表)

[1. 为什么有"终结方法"?](#1. 为什么有“终结方法”?)

[2. 响应式编程](#2. 响应式编程)

[3.5 运行代码生成](#3.5 运行代码生成)

[4. ⚛️ 工程化封装](#4. ⚛️ 工程化封装)

[4.1 Bean 层:定义表结构](#4.1 Bean 层:定义表结构)

[4.2 DAO 层:封装业务逻辑](#4.2 DAO 层:封装业务逻辑)

[4.3 DB 层:数据库引擎与迁移](#4.3 DB 层:数据库引擎与迁移)

[4.4 Manager 层:单例管理](#4.4 Manager 层:单例管理)

[5. ✅ 小结](#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.9

3.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) | where(), orderBy(), limit() | .get() | Future<List<T>> |
| 查询 | select(table) | where(), orderBy() | .watch() | Stream<List<T>> |
| 查询 | select(table) | where() | .getSingle() | Future<T> |
| 插入 | into(table) | 无 | .insert(companion) | Future<int> (新ID) |
| 删除 | delete(table) | where() | .go() | Future<int> (行数) |
| 更新 | update(table) | where() | .write(companion) | Future<int> (行数) |
| 更新 | update(table) | 无 | .replace(entity) | Future<bool> |

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-outputs

Tips: 建议将生成的 .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 的安全完美结合。通过本篇的模块化封装,我们不仅隔离了底层复杂度,还为后续的业务扩展打下了坚实基础。

另外,由于本人能力有限,如有错误,敬请批评指正,谢谢。

相关推荐
做cv的小昊9 小时前
【TJU】信息检索与分析课程笔记和练习(6)英文数据库检索—web of science
大数据·数据库·笔记·学习·全文检索
严同学正在努力9 小时前
VMware安装银河麒麟V10操作系统X86_64全过程
数据库·鸿蒙系统·kylin
智源研究院官方账号9 小时前
众智FlagOS 1.6发布,以统一架构推动AI硬件、软件技术生态创新发展
数据库·人工智能·算法·架构·编辑器·硬件工程·开源软件
yfmingo9 小时前
flutter 哪些任务是在微队列,哪些是在事件队列
flutter
dishugj10 小时前
[SQLSERVER] Lock Waits/sec参数含义详解
数据库·oracle·sqlserver
我科绝伦(Huanhuan Zhou)10 小时前
Oracle锁等待深度解析:从理论到实战的全方位指南
数据库·oracle
小Mie不吃饭10 小时前
Oracle vs MySQL 全面对比分析
数据库·mysql·oracle
我科绝伦(Huanhuan Zhou)10 小时前
KingbaseES数据库备份与恢复深度解析:原理、策略与实践
数据库·金仓数据库
烤鱼骑不快10 小时前
ubuntu系统安装以及设置
linux·数据库·ubuntu
BORN(^-^)10 小时前
达梦数据库索引删除操作小记
数据库·达梦