跳转到正文
YGJoon 技术博客

Flutter SQLite 数据库详解

发布于:
·1.5k 字 · 约 4 分钟·作者:
sqflite 单例封装 DatabaseHelper:建表、增删改查、事务、版本升级 onUpgrade,一套本地关系型存储的完整套路
Flutter SQLite 数据库详解文章封面
目录

简介

SQLite 是一个轻量级的关系型数据库,Flutter 通过 sqflite 插件提供了 SQLite 数据库支持。

基本配置

添加依赖

dependencies:
  sqflite: ^2.3.0
  path: ^1.8.3

初始化数据库

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  static Database? _database;
  
  factory DatabaseHelper() => _instance;
  
  DatabaseHelper._internal();
  
  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }
  
  Future<Database> _initDatabase() async {
    // 获取数据库路径
    String path = join(await getDatabasesPath(), 'my_app.db');
    
    // 打开数据库
    return await openDatabase(
      path,
      version: 1,
      onCreate: _onCreate,
      onUpgrade: _onUpgrade,
    );
  }
  
  Future<void> _onCreate(Database db, int version) async {
    // 创建表
    await db.execute('''
      CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        age INTEGER
      )
    ''');
  }
  
  Future<void> _onUpgrade(
    Database db,
    int oldVersion,
    int newVersion,
  ) async {
    // 数据库升级逻辑
  }
}

基本操作

插入数据

Future<int> insertUser(User user) async {
  final db = await database;
  return await db.insert(
    'users',
    user.toMap(),
    conflictAlgorithm: ConflictAlgorithm.replace,
  );
}

查询数据

Future<List<User>> getUsers() async {
  final db = await database;
  final List<Map<String, dynamic>> maps = await db.query('users');
  
  return List.generate(maps.length, (i) {
    return User.fromMap(maps[i]);
  });
}

Future<User?> getUserById(int id) async {
  final db = await database;
  final List<Map<String, dynamic>> maps = await db.query(
    'users',
    where: 'id = ?',
    whereArgs: [id],
  );
  
  if (maps.isEmpty) return null;
  return User.fromMap(maps.first);
}

更新数据

Future<int> updateUser(User user) async {
  final db = await database;
  return await db.update(
    'users',
    user.toMap(),
    where: 'id = ?',
    whereArgs: [user.id],
  );
}

删除数据

Future<int> deleteUser(int id) async {
  final db = await database;
  return await db.delete(
    'users',
    where: 'id = ?',
    whereArgs: [id],
  );
}

完整示例

// 用户模型
class User {
  final int? id;
  final String name;
  final String email;
  final int age;
  
  User({
    this.id,
    required this.name,
    required this.email,
    required this.age,
  });
  
  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'name': name,
      'email': email,
      'age': age,
    };
  }
  
  factory User.fromMap(Map<String, dynamic> map) {
    return User(
      id: map['id'],
      name: map['name'],
      email: map['email'],
      age: map['age'],
    );
  }

  User copyWith({int? id, String? name, String? email, int? age}) {
    return User(
      id: id ?? this.id,
      name: name ?? this.name,
      email: email ?? this.email,
      age: age ?? this.age,
    );
  }
}

// 数据库管理类
class DatabaseManager {
  static final DatabaseManager _instance = DatabaseManager._internal();
  final dbHelper = DatabaseHelper();
  
  factory DatabaseManager() => _instance;
  
  DatabaseManager._internal();
  
  // 添加用户
  Future<User> addUser(User user) async {
    final id = await dbHelper.insertUser(user);
    return user.copyWith(id: id);
  }
  
  // 获取所有用户
  Future<List<User>> getAllUsers() async {
    return await dbHelper.getUsers();
  }
  
  // 获取单个用户
  Future<User?> getUser(int id) async {
    return await dbHelper.getUserById(id);
  }
  
  // 更新用户
  Future<bool> updateUser(User user) async {
    final count = await dbHelper.updateUser(user);
    return count > 0;
  }
  
  // 删除用户
  Future<bool> deleteUser(int id) async {
    final count = await dbHelper.deleteUser(id);
    return count > 0;
  }
}

// 使用示例
class UserListPage extends StatefulWidget {
  @override
  _UserListPageState createState() => _UserListPageState();
}

class _UserListPageState extends State<UserListPage> {
  final dbManager = DatabaseManager();
  List<User> _users = [];
  
  @override
  void initState() {
    super.initState();
    _loadUsers();
  }
  
  Future<void> _loadUsers() async {
    final users = await dbManager.getAllUsers();
    setState(() {
      _users = users;
    });
  }
  
  Future<void> _addUser() async {
    final user = User(
      name: 'John Doe',
      email: '[email protected]',
      age: 30,
    );
    
    await dbManager.addUser(user);
    _loadUsers();
  }
  
  Future<void> _deleteUser(int id) async {
    await dbManager.deleteUser(id);
    _loadUsers();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Users')),
      body: ListView.builder(
        itemCount: _users.length,
        itemBuilder: (context, index) {
          final user = _users[index];
          return ListTile(
            title: Text(user.name),
            subtitle: Text(user.email),
            trailing: IconButton(
              icon: Icon(Icons.delete),
              onPressed: () => _deleteUser(user.id!),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addUser,
        child: Icon(Icons.add),
      ),
    );
  }
}

事务处理

Future<void> transferMoney(
  int fromId,
  int toId,
  double amount,
) async {
  final db = await database;
  await db.transaction((txn) async {
    // 扣除转出账户金额
    await txn.rawUpdate('''
      UPDATE accounts
      SET balance = balance - ?
      WHERE id = ?
    ''', [amount, fromId]);
    
    // 增加转入账户金额
    await txn.rawUpdate('''
      UPDATE accounts
      SET balance = balance + ?
      WHERE id = ?
    ''', [amount, toId]);
  });
}

最佳实践

  1. 批量写用 batch(),别循环调 insert:几百条数据逐条插入是几百次独立事务,慢一个数量级;包进一个 batch 一次提交,差距肉眼可见。
  2. 单表行数上万、或 where/order by 的字段没建索引,查询会明显变慢:给高频查询字段加索引,但别乱加——写多读少的表,每多一个索引就多一份写入成本。
  3. onUpgrade 里只做 ALTER TABLE 加字段和数据迁移,千万别 DROP TABLE 重建:用户手机里的数据不是你的测试数据,删了找不回来。
  4. 数据库连接保持单例常开(文中 DatabaseHelper 已是单例),别每次操作都 open/close:openDatabase 有文件锁和初始化开销,频繁开关反而更容易撞上 “database is locked”。

注意事项

  1. 别在 UI 线程做大查询:一次查几千行再转成对象会卡帧,要么分页查(limit/offset),要么丢进 isolate。
  2. version 每发版只增不减,且 onUpgrade 要处理”隔代升级”:用户可能从 v1 直接升到 v3,oldVersion == 1 的分支必须还能走到,别只写 if (oldVersion == 2)。
  3. 并发写要收敛:sqflite 同一个 Database 实例内部是串行的,但两个 isolate 各 open 一次就会锁冲突——写操作统一走单例,别多头 open。
  4. 删改查的 where 别拼字符串:搜索框内容拼进 rawQuery 就是 SQL 注入的口子,一律用 whereArgs 参数化。

实战案例:校准数据到底存哪儿

在我的 flutter_brightness 项目(Windows 桌面应用,串口读光照传感器、自动调屏幕亮度)里,用户标定的光照-亮度对应点存在 lib/services/database_service.dart,模型是 lib/models/lux_data.dart 里的 LuxData 和 CalibrationPoint。一开始我纠结过:校准点撑死十几条,写个 JSON 文件不就行了?最后还是单独拆了个 service 文件。原因是校准点不是”写一次读一次”的配置——用户会反复增删、按光照值排序取区间,JSON 文件每次全量读写,越到后面越别扭。trade-off 是:数据量这么小其实 SQLite 和 JSON 性能没差别,选 service 文件纯粹是为了”按需读写单条”的语义清晰。另一个坚持是模型和存储分开:lux_data.dart 只管数据结构,brightness_mapping.dart 的映射算法保持纯函数,不碰 IO,这样以后想把存储换成别的方式,算法层一行不用动。小项目的数据持久化,选型看的不是数据量,而是读写模式。

总结

SQLite 数据库为 Flutter 应用提供了强大的数据持久化能力。通过合理使用数据库特性,可以实现高效可靠的数据存储和管理。


分享这篇文章:


相关文章

上一篇
Flutter WebSocket 实现详解
下一篇
Flutter Stack 组件详解