一、为什么选择Dart和SQLite这对黄金搭档

在移动开发领域,本地数据存储就像是你手机里的记事本——随时要存,随时要取,还得保证速度快、不卡顿。Dart作为Flutter的御用语言,搭配轻量级数据库SQLite,简直是天作之合。SQLite不需要服务器,单个文件就能搞定所有数据存储,而Dart通过sqflite插件可以轻松操作它。

举个实际场景:你正在开发一个记账App,用户每次输入收支记录都需要立刻保存,这时候网络可能不稳定,本地存储就成了刚需。下面这段代码展示了如何初始化SQLite数据库:

// 技术栈:Flutter + Dart + sqflite
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

Future<Database> initDatabase() async {
  // 获取数据库存储路径
  final dbPath = await getDatabasesPath();
  final path = join(dbPath, 'my_account.db');
  
  // 打开数据库(如果不存在则创建)
  return openDatabase(
    path,
    version: 1,
    onCreate: (db, version) {
      // 创建记账记录表
      return db.execute('''
        CREATE TABLE accounts(
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          title TEXT,
          amount REAL,
          category TEXT,
          date TEXT
        )
      ''');
    },
  );
}

注释说明

  • getDatabasesPath():获取设备上的数据库存储目录
  • AUTOINCREMENT:让主键ID自动增长,避免手动管理

二、CRUD操作:从入门到熟练

数据库的核心操作无非增删改查(CRUD),但细节决定体验。比如插入数据时要考虑事务,查询时要支持排序和分页。下面是一个完整的操作示例:

// 插入一条记账记录(带事务)
Future<void> insertRecord(Account record) async {
  final db = await initDatabase();
  await db.transaction((txn) async {
    await txn.insert(
      'accounts',
      record.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  });
}

// 查询最近10条支出记录(按日期倒序)
Future<List<Account>> queryRecentExpenses() async {
  final db = await initDatabase();
  final maps = await db.query(
    'accounts',
    where: 'amount < 0',  // 支出为负数
    orderBy: 'date DESC',
    limit: 10,
  );
  return maps.map((map) => Account.fromMap(map)).toList();
}

避坑指南

  1. 始终用transaction处理批量操作,避免数据不一致
  2. ConflictAlgorithm.replace可以在主键冲突时自动覆盖旧数据

三、高级技巧:数据库迁移与性能优化

当你发布新版本需要修改表结构时,直接删库用户会骂街的。正确的做法是数据库迁移。比如我们要给记账表增加tag字段:

Future<void> migrateDatabase(Database db, int oldVersion, int newVersion) async {
  if (oldVersion < 2) {
    await db.execute('ALTER TABLE accounts ADD COLUMN tag TEXT DEFAULT ""');
  }
}

// 初始化时指定迁移回调
openDatabase(
  path,
  version: 2,  // 版本号升级
  onUpgrade: migrateDatabase,
);

性能优化三原则

  1. batch批量操作比单条执行快10倍以上
  2. 频繁查询的字段记得加索引
  3. 关闭数据库时调用close()防止内存泄漏

四、实战中的那些坑与解决方案

  1. 并发问题:多个页面同时操作数据库可能导致锁冲突。解决方案是用单例模式管理数据库连接:
class DatabaseHelper {
  static Database? _db;
  Future<Database> get database async {
    if (_db != null) return _db!;
    _db = await initDatabase();
    return _db!;
  }
}
  1. 数据类型映射:SQLite只有5种基础类型,处理日期时需要转换:
// 存储时转为字符串
record.toMap()..['date'] = record.date.toIso8601String();

// 读取时解析回来
Account.fromMap(map)..date = DateTime.parse(map['date']);
  1. 加密需求:敏感数据可以使用sqlcipher插件加密整个数据库文件

五、什么时候该用SQLite?什么时候该换方案?

适合场景

  • 个人离线应用(如日记、记账)
  • 需要复杂查询的中小型数据集(<10万条)
  • 要求毫秒级响应速度的读写操作

不适合场景

  • 需要多设备同步的数据(考虑Firebase)
  • 超大规模数据存储(考虑Hive或对象存储)
  • 高频写入的日志类数据(考虑文件直接存储)

六、总结

Dart和SQLite的组合就像咖啡配奶糖——简单却能调出专业味道。记住几个关键点:事务保证安全、迁移保持兼容、索引提升速度。下次当你需要在Flutter应用中存储用户数据时,不妨先试试这个轻量级方案,它可能会给你意想不到的惊喜。