FlutterSQLite数据库详解

Flutter SQLite 数据库详解

在移动应用开发中,本地数据存储是不可或缺的一部分。Flutter 提供了多种本地数据存储方案,其中 SQLite 以其轻量级、高效、易用等特点,成为许多开发者的首选。本文将深入探讨 Flutter 中 SQLite 数据库的使用,包括集成、创建、增删改查、事务处理、版本管理等关键方面。

一、集成 SQLite 插件

Flutter 官方推荐使用 sqflite 插件来操作 SQLite 数据库。首先,在 pubspec.yaml 文件中添加依赖:

yaml
dependencies:
sqflite: ^2.2.8+4 # 建议使用最新版本
path_provider: ^2.0.15 # 用于获取数据库文件路径
path: ^1.8.3 # 路径处理

然后,在终端运行 flutter pub get 下载依赖。

二、创建和打开数据库

使用 sqflite 插件,我们可以轻松创建和打开数据库。以下是一个完整的示例,展示了数据库的创建、打开、关闭以及简单的表创建:

```dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';

class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
static Database? _database;

DatabaseHelper._privateConstructor();

Future get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}

Future _initDatabase() async {
// 获取应用文档目录
final documentsDirectory = await getApplicationDocumentsDirectory();
// 构建数据库文件路径
final path = join(documentsDirectory.path, 'my_database.db');

// 打开数据库(如果不存在则创建)
return await openDatabase(
  path,
  version: 1, // 数据库版本号
  onCreate: _onCreate, // 数据库创建时的回调函数
);

}

// 创建数据库表
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
)
''');
}

// 关闭数据库(通常不需要手动关闭,除非你需要立即释放资源)
Future closeDatabase() async {
if (_database != null) {
await _database!.close();
_database = null;
}
}
}
```

代码解析:

  • DatabaseHelper 类: 使用单例模式确保全局只有一个数据库实例。
  • _initDatabase() 方法:
    • 使用 path_provider 获取应用文档目录。
    • 使用 path 库构建数据库文件路径。
    • 使用 openDatabase() 打开或创建数据库。
      • version:指定数据库版本号,用于数据库升级。
      • onCreate:当数据库首次创建时执行的回调函数,通常用于创建表。
  • _onCreate() 方法: 执行 SQL 语句创建 users 表。
    • id:自增主键。
    • name:文本类型,不能为空。
    • age:整数类型。
  • closeDatabase()方法: 关闭数据库连接. (通常不需要手动调用, 在应用生命周期结束时会自动关闭).

三、数据库操作(CRUD)

有了数据库连接,我们就可以进行数据的增删改查(CRUD)操作了。

1. 插入数据 (Create)

dart
Future<int> insertUser(String name, int age) async {
final db = await instance.database;
return await db.insert(
'users',
{'name': name, 'age': age},
conflictAlgorithm: ConflictAlgorithm.replace, // 如果主键冲突,则替换现有数据
);
}

  • db.insert() 方法用于插入数据。
    • 第一个参数是表名。
    • 第二个参数是一个 Map 对象,键是列名,值是要插入的数据。
    • conflictAlgorithm 参数用于处理插入时可能发生的主键冲突。
      • ConflictAlgorithm.replace:替换现有数据。
      • ConflictAlgorithm.ignore:忽略冲突,不插入新数据。
      • ConflictAlgorithm.fail:抛出异常。
      • ConflictAlgorithm.abort:回滚事务 (默认值).
      • ConflictAlgorithm.rollback:回滚事务.

2. 查询数据 (Read)

```dart
Future>> getUsers() async {
final db = await instance.database;
return await db.query('users');
}

Future?> getUserById(int id) async {
final db = await instance.database;
final List> result = await db.query(
'users',
where: 'id = ?',
whereArgs: [id],
);
return result.isNotEmpty ? result.first : null;
}

// 使用 rawQuery 执行更复杂的查询
Future>> getUsersWithAgeGreaterThan(int age) async {
final db = await DatabaseHelper.instance.database;
return await db.rawQuery('SELECT * FROM users WHERE age > ?', [age]);
}

```

  • db.query() 方法用于查询数据。
    • 可以指定 wherewhereArgs 参数进行条件查询。
    • 返回一个 List<Map<String, dynamic>>,每个 Map 代表一行数据。
  • db.rawQuery() 方法允许执行原始 SQL 查询语句,提供更大的灵活性。

3. 更新数据 (Update)

dart
Future<int> updateUser(int id, String name, int age) async {
final db = await instance.database;
return await db.update(
'users',
{'name': name, 'age': age},
where: 'id = ?',
whereArgs: [id],
);
}

  • db.update() 方法用于更新数据。
    • 第一个参数是表名。
    • 第二个参数是一个 Map 对象,包含要更新的列和值。
    • wherewhereArgs 参数用于指定更新条件。
    • 返回受影响的行数。

4. 删除数据 (Delete)

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

  • db.delete() 方法用于删除数据。
    • wherewhereArgs 参数用于指定删除条件。
    • 返回受影响的行数。

四、事务处理

为了保证数据的一致性和完整性,SQLite 支持事务处理。事务可以将多个数据库操作作为一个原子操作,要么全部成功,要么全部失败。

```dart
Future performTransaction() async {
final db = await instance.database;
await db.transaction((txn) async {
// 在事务中执行多个操作
await txn.insert('users', {'name': 'Alice', 'age': 30});
await txn.insert('users', {'name': 'Bob', 'age': 25});

// 模拟一个错误,导致事务回滚
// throw Exception('Simulated error');

});
}
```

  • db.transaction() 方法用于开启一个事务。
  • 在事务回调函数中执行多个数据库操作。
  • 如果事务中的任何操作失败(例如抛出异常),整个事务将回滚,所有操作都不会生效。
  • 如果事务中的所有操作都成功,则事务提交,所有操作都会生效。

五、数据库版本管理和升级

在应用开发过程中,数据库结构可能会发生变化(例如添加新表、修改表结构)。为了处理这些变化,我们需要进行数据库版本管理和升级。

  • version 参数:openDatabase() 方法中,version 参数指定了数据库的版本号。每次数据库结构发生变化时,都应该增加版本号。
  • onUpgrade 回调函数:openDatabase() 检测到当前版本号高于数据库文件的版本号时,会调用 onUpgrade 回调函数。我们可以在 onUpgrade 函数中执行 SQL 语句来更新数据库结构。
  • onDowngrade 回调函数:openDatabase() 检测到当前版本号低于数据库文件的版本号时,会调用 onDowngrade 回调函数. 通常情况下, onDowngrade 应该抛出异常, 因为降级可能会导致数据丢失. 可以使用 onDowngrade: onDatabaseDowngradeDelete.

```dart
// 修改 _initDatabase 方法
Future _initDatabase() async {
final documentsDirectory = await getApplicationDocumentsDirectory();
final path = join(documentsDirectory.path, 'my_database.db');

return await openDatabase(
path,
version: 2, // 更新版本号
onCreate: _onCreate,
onUpgrade: _onUpgrade, // 添加 onUpgrade 回调函数
onDowngrade: onDatabaseDowngradeDelete, // 示例: 降级时删除数据库
);
}

// 添加 _onUpgrade 方法
Future _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
// 从版本 1 升级到版本 2
await db.execute('ALTER TABLE users ADD COLUMN email TEXT');
}
// 可以添加更多的 if 语句来处理其他版本升级
}

```

代码解析:

  • _initDatabase() 方法中,将 version 参数更新为 2
  • 添加 onUpgrade 回调函数 _onUpgrade
  • _onUpgrade 函数中,根据 oldVersionnewVersion 判断需要执行哪些升级操作。
    • 在这个例子中,如果 oldVersion 小于 2,则执行 ALTER TABLE 语句为 users 表添加 email 列。
  • 使用了 onDowngrade: onDatabaseDowngradeDelete, 表示如果数据库版本降级,则删除并重新创建数据库。 更精细的控制需要自行编写 onDowngrade 函数。

六、总结

本文详细介绍了 Flutter 中 SQLite 数据库的使用,包括集成、创建、增删改查、事务处理、版本管理等方面。通过掌握这些知识,开发者可以轻松地在 Flutter 应用中实现本地数据存储,为用户提供更好的离线体验和数据持久化能力。

进阶提示:

  • 数据库加密: 可以使用 sqlcipher_flutter_libs 插件实现数据库加密,保护用户数据安全。
  • 复杂查询: sqflite 支持复杂的 SQL 查询,包括 JOIN、子查询、聚合函数等。
  • 数据库备份和恢复: 可以使用 path_provider 获取数据库文件路径,然后进行备份和恢复操作。
  • 使用 ORM (Object-Relational Mapping): 可以考虑使用一些 ORM 框架,例如 moor,来简化数据库操作,提高开发效率。 Moor 还支持响应式查询。
  • 多线程: 当处理大量数据时,可以考虑将数据库操作放在后台线程中执行,避免阻塞 UI 线程。

希望本文能够帮助你更好地理解和使用 Flutter 中的 SQLite 数据库。

THE END