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 JOIN、GROUP 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...