cd ..
2025-12-0412 min58 views

移动端 Flutter 本地存储双雄:Isar 与 Drift 选型实战指南

#Sql#Database#NoSql#Orm#Flutter

一、 NoSQL 派系首选:Isar Database

Isar 是目前 Flutter 社区公认的 NoSQL 性能怪兽。

1. 核心定位
  • 极致性能:专门为 Flutter 优化的数据库,核心使用 C++ 编写(通过 Dart FFI 调用),读写速度极快。
  • 对象导向:非常适合直接存储服务器返回的 JSON 转换后的实体对象(Entity)。
  • 特性:支持 ACID 事务、全文搜索、复杂的联合索引。
2. 为什么选它?

如果你的数据主要是**“列表展示”**(如新闻列表、商品流、聊天记录),且数据之间的关联关系不复杂(不需要复杂的 Join),Isar 是首选。它的开发体验非常像操作 Java 的 List

3. 代码体验

你看,定义一个实体非常像 Java 的注解:

@collection
class User {
  Id id = Isar.autoIncrement; // 自动生成主键

  @Index(type: IndexType.value) //以此字段建立索引
  late String name;

  int? age;
}

// 使用:简直就像 Java Stream API
final users = await isar.users.filter()
  .nameStartsWith('Zhang')
  .ageGreaterThan(18)
  .sortByAgeDesc()
  .findAll();

二、 关系型 (SQL) 派系首选:Drift

Drift (原名 Moor) 是 Flutter 上的所谓“现代 SQL 库”。它不是简单的 SQLite 包装器,而是一个带有强类型检查的 ORM。

1. 核心定位
  • 类型安全:它会在编译时检查你的 SQL 语句(或 Dart DSL)是否正确。
  • JPA 的亲兄弟:如果你习惯了 Java 的 JPA/Hibernate,你会对 Drift 感到亲切。它支持 Table 定义、DAO 模式、外键约束。
  • 响应式:查询结果可以直接转化为 Stream,数据一变,UI 自动刷新。
2. 为什么选它?

如果你的业务逻辑非常复杂,数据之间有强关联(例如:一个复杂的 ERP 系统,订单 关联 商品商品 关联 库存库存 关联 仓库),需要用到 INNER JOINGROUP BY 等复杂 SQL 操作,Drift 是唯一解。

3. 代码体验

它允许你用 Dart 语法写 SQL(避免拼写错误的字符串):

// 定义表结构
class Todos extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text().withLength(min: 6, max: 32)();
  TextColumn get content => text().named('body')();
  IntColumn get category => integer().nullable()(); // 外键
}

// 使用:强类型的查询构建
Future<List<Todo>> findTodos(int categoryId) {
  return (select(todos)..where((t) => t.category.equals(categoryId))).get();
}

三、 终极选型指南:如何决策?

为了帮你做决定,我整理了这个对比维度:

维度 Isar (NoSQL) Drift (SQL)
思维模式 对象思维。不仅存数据,更像是在持久化 Dart 对象。 关系思维。表、行、列、外键、Join。
开发速度 极快。配置少,注解加上就能用。 中等。需要定义表结构,生成代码,写迁移脚本。
数据结构 适合非结构化或层级较深的数据(嵌套对象)。 适合高度结构化、扁平化、强关联的数据。
迁移成本 字段增删很宽容,改动 Entity 即可。 严格的 Schema 变更,需要写 Schema Migration (版本号控制)。
推荐场景 大多数 App(博客、新闻、社交、电商展示层)。 离线工具类 App(记账本、库存管理、本地复杂报表)。

四、 架构建议:缓存策略

无论你选哪个,针对你“减轻服务器压力”的需求,建议采用 Repository 模式 + 单一数据源 (Single Source of Truth) 策略。

流程伪代码:

// UserRepo
Stream<User> getUser(String id) async* {
  // 1. 立即发射本地数据库的旧数据(让用户先看到东西,不转圈)
  final localUser = await _localDb.getUser(id);
  if (localUser != null) yield localUser;

  try {
    // 2. 悄悄去服务器拉最新数据
    final remoteUser = await _api.fetchUser(id);
    
    // 3. 写入本地数据库(这一步会触发数据库的 Stream 通知,UI 会自动再次刷新)
    await _localDb.saveUser(remoteUser); 
  } catch (e) {
    // 处理网络错误,但用户依然能看到旧数据
  }
}

/** Comments(0)*/

Loading comments...