Skip to main content

使用 SQLite 持久化数据

如果您正在编写一个需要在本地设备上持久化和查询大量数据的应用程序,请考虑使用数据库而不是本地文件或键值存储。一般来说,与其他本地持久化解决方案相比,数据库提供更快的插入、更新和查询速度。

Flutter 应用可以通过 pub.dev 上提供的 sqflite 插件使用 SQLite 数据库。本示例演示了使用 sqflite 插入、读取、更新和删除各种狗的数据的基础知识。

如果您不熟悉 SQLite 和 SQL 语句,请在完成本示例之前,查看SQLite 教程以了解基础知识。

本示例使用以下步骤:

  1. 添加依赖项。
  2. 定义 Dog 数据模型。
  3. 打开数据库。
  4. 创建 dogs 表。
  5. Dog 插入数据库。
  6. 检索狗的列表。
  7. 更新数据库中的 Dog
  8. 从数据库中删除 Dog

1. 添加依赖项

#

要使用 SQLite 数据库,请导入 sqflitepath 包。

  • sqflite 包提供与 SQLite 数据库交互的类和函数。
  • path 包提供定义数据库在磁盘上存储位置的函数。

要添加包作为依赖项,请运行 flutter pub add

flutter pub add sqflite path

确保在您将要使用的文件中导入这些包。

dart
import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

2. 定义 Dog 数据模型

#

在创建用于存储狗信息的表之前,请花一些时间定义需要存储的数据。对于此示例,定义一个包含三个数据片段的 Dog 类:唯一的 idname 和每条狗的 age

dart
class Dog {
  final int id;
  final String name;
  final int age;

  const Dog({
    required this.id,
    required this.name,
    required this.age,
  });
}

3. 打开数据库

#

在读取和写入数据库数据之前,请打开与数据库的连接。这包括两个步骤:

  1. 使用 sqflite 包中的 getDatabasesPath() 函数以及 path 包中的 join 函数定义数据库文件的路径。
  2. 使用 sqflite 中的 openDatabase() 函数打开数据库。
dart
// 避免因 flutter upgrade 引起的错误。
// 需要导入 'package:flutter/widgets.dart'。
WidgetsFlutterBinding.ensureInitialized();
// 打开数据库并存储引用。
final database = openDatabase(
  // 设置数据库的路径。注意:使用 `path` 包中的 `join` 函数是最佳实践,以确保为每个平台正确构造路径。
  join(await getDatabasesPath(), 'doggie_database.db'),
);

4. 创建 dogs

#

接下来,创建一个表来存储各种狗的信息。对于此示例,创建一个名为 dogs 的表,该表定义可以存储的数据。每个 Dog 包含一个 idnameage。因此,这些在 dogs 表中表示为三列。

  1. id 是一个 Dart int,并存储为 INTEGER SQLite 数据类型。最好也使用 id 作为表的主键,以提高查询和更新时间。
  2. name 是一个 Dart String,并存储为 TEXT SQLite 数据类型。
  3. age 也是一个 Dart int,并存储为 INTEGER 数据类型。

有关可以在 SQLite 数据库中存储的可用数据类型的更多信息,请参阅官方 SQLite 数据类型文档

dart
final database = openDatabase(
  // 设置数据库的路径。注意:使用 `path` 包中的 `join` 函数是最佳实践,以确保为每个平台正确构造路径。
  join(await getDatabasesPath(), 'doggie_database.db'),
  // 当数据库第一次创建时,创建一个表来存储狗。
  onCreate: (db, version) {
    // 在数据库上运行 CREATE TABLE 语句。
    return db.execute(
      'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
    );
  },
  // 设置版本。这将执行 onCreate 函数并提供执行数据库升级和降级的路径。
  version: 1,
);

5. 将 Dog 插入数据库

#

现在您有一个包含适合存储各种狗信息的表的数据库,现在该读取和写入数据了。

首先,将 Dog 插入 dogs 表。这包括两个步骤:

  1. Dog 转换为 Map
  2. 使用 insert() 方法将 Map 存储在 dogs 表中。
dart
class Dog {
  final int id;
  final String name;
  final int age;

  Dog({
    required this.id,
    required this.name,
    required this.age,
  });

  // 将 Dog 转换为 Map。键必须与数据库中列的名称相对应。
  Map<String, Object?> toMap() {
    return {
      'id': id,
      'name': name,
      'age': age,
    };
  }

  // 实现 toString 以便在使用 print 语句时更容易查看有关每条狗的信息。
  @override
  String toString() {
    return 'Dog{id: $id, name: $name, age: $age}';
  }
}
dart
// 定义一个将狗插入数据库的函数
Future<void> insertDog(Dog dog) async {
  // 获取数据库的引用。
  final db = await database;

  // 将 Dog 插入正确的表中。您也可以指定在两次插入相同的狗时使用的 `conflictAlgorithm`。
  //
  // 在这种情况下,替换任何以前的数据。
  await db.insert(
    'dogs',
    dog.toMap(),
    conflictAlgorithm: ConflictAlgorithm.replace,
  );
}
dart
// 创建一条狗并将其添加到 dogs 表
var fido = Dog(
  id: 0,
  name: 'Fido',
  age: 35,
);

await insertDog(fido);

6. 检索狗的列表

#

现在数据库中已经存储了一条 Dog,请查询数据库以获取特定狗或所有狗的列表。这包括两个步骤:

  1. dogs 表运行 query。这将返回一个 List<Map>
  2. List<Map> 转换为 List<Dog>
dart
// 一个从 dogs 表检索所有狗的方法。
Future<List<Dog>> dogs() async {
  // 获取数据库的引用。
  final db = await database;

  // 查询表中的所有狗。
  final List<Map<String, Object?>> dogMaps = await db.query('dogs');

  // 将每条狗的字段列表转换为 `Dog` 对象列表。
  return [
    for (final {
          'id': id as int,
          'name': name as String,
          'age': age as int,
        } in dogMaps)
      Dog(id: id, name: name, age: age),
  ];
}
dart
// 现在,使用上述方法检索所有狗。
print(await dogs()); // 打印包含 Fido 的列表。

7. 更新数据库中的 Dog

#

在将信息插入数据库后,您可能希望稍后更新该信息。您可以通过使用 sqflite 库中的 update() 方法来实现。

这包括两个步骤:

  1. 将 Dog 转换为 Map。
  2. 使用 where 子句确保更新正确的 Dog。
dart
Future<void> updateDog(Dog dog) async {
  // 获取数据库的引用。
  final db = await database;

  // 更新给定的 Dog。
  await db.update(
    'dogs',
    dog.toMap(),
    // 确保 Dog 具有匹配的 id。
    where: 'id = ?',
    // 传递 Dog 的 id 作为 whereArg 以防止 SQL 注入。
    whereArgs: [dog.id],
  );
}
dart
// 更新 Fido 的年龄并将其保存到数据库。
fido = Dog(
  id: fido.id,
  name: fido.name,
  age: fido.age + 7,
);
await updateDog(fido);

// 打印更新的结果。
print(await dogs()); // 打印年龄为 42 的 Fido。

8. 从数据库中删除 Dog

#

除了插入和更新有关狗的信息外,您还可以从数据库中删除狗。要删除数据,请使用 sqflite 库中的 delete() 方法。

在本节中,创建一个函数,该函数接受一个 id 并从数据库中删除具有匹配 id 的狗。要使其工作,您必须提供一个 where 子句来限制被删除的记录。

dart
Future<void> deleteDog(int id) async {
  // 获取数据库的引用。
  final db = await database;

  // 从数据库中删除 Dog。
  await db.delete(
    'dogs',
    // 使用 `where` 子句删除特定的狗。
    where: 'id = ?',
    // 传递 Dog 的 id 作为 whereArg 以防止 SQL 注入。
    whereArgs: [id],
  );
}

示例

#

要运行示例:

  1. 创建一个新的 Flutter 项目。
  2. sqflitepath 包添加到您的 pubspec.yaml
  3. 将以下代码粘贴到名为 lib/db_test.dart 的新文件中。
  4. 使用 flutter run lib/db_test.dart 运行代码。
dart
import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

void main() async {
  // 避免因 flutter upgrade 引起的错误。
  // 需要导入 'package:flutter/widgets.dart'。
  WidgetsFlutterBinding.ensureInitialized();
  // 打开数据库并存储引用。
  final database = openDatabase(
    // 设置数据库的路径。注意:使用 `path` 包中的 `join` 函数是最佳实践,以确保为每个平台正确构造路径。
    join(await getDatabasesPath(), 'doggie_database.db'),
    // 当数据库第一次创建时,创建一个表来存储狗。
    onCreate: (db, version) {
      // 在数据库上运行 CREATE TABLE 语句。
      return db.execute(
        'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
      );
    },
    // 设置版本。这将执行 onCreate 函数并提供执行数据库升级和降级的路径。
    version: 1,
  );

  // 定义一个将狗插入数据库的函数
  Future<void> insertDog(Dog dog) async {
    // 获取数据库的引用。
    final db = await database;

    // 将 Dog 插入正确的表中。您也可以指定在两次插入相同的狗时使用的 `conflictAlgorithm`。
    //
    // 在这种情况下,替换任何以前的数据。
    await db.insert(
      'dogs',
      dog.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  // 一个从 dogs 表检索所有狗的方法。
  Future<List<Dog>> dogs() async {
    // 获取数据库的引用。
    final db = await database;

    // 查询表中的所有狗。
    final List<Map<String, Object?>> dogMaps = await db.query('dogs');

    // 将每条狗的字段列表转换为 `Dog` 对象列表。
    return [
      for (final {
            'id': id as int,
            'name': name as String,
            'age': age as int,
          } in dogMaps)
        Dog(id: id, name: name, age: age),
    ];
  }

  Future<void> updateDog(Dog dog) async {
    // 获取数据库的引用。
    final db = await database;

    // 更新给定的 Dog。
    await db.update(
      'dogs',
      dog.toMap(),
      // 确保 Dog 具有匹配的 id。
      where: 'id = ?',
      // 传递 Dog 的 id 作为 whereArg 以防止 SQL 注入。
      whereArgs: [dog.id],
    );
  }

  Future<void> deleteDog(int id) async {
    // 获取数据库的引用。
    final db = await database;

    // 从数据库中删除 Dog。
    await db.delete(
      'dogs',
      // 使用 `where` 子句删除特定的狗。
      where: 'id = ?',
      // 传递 Dog 的 id 作为 whereArg 以防止 SQL 注入。
      whereArgs: [id],
    );
  }

  // 创建一条狗并将其添加到 dogs 表
  var fido = Dog(
    id: 0,
    name: 'Fido',
    age: 35,
  );

  await insertDog(fido);

  // 现在,使用上述方法检索所有狗。
  print(await dogs()); // 打印包含 Fido 的列表。

  // 更新 Fido 的年龄并将其保存到数据库。
  fido = Dog(
    id: fido.id,
    name: fido.name,
    age: fido.age + 7,
  );
  await updateDog(fido);

  // 打印更新的结果。
  print(await dogs()); // 打印年龄为 42 的 Fido。

  // 从数据库中删除 Fido。
  await deleteDog(fido.id);

  // 打印狗的列表(空)。
  print(await dogs());
}

class Dog {
  final int id;
  final String name;
  final int age;

  Dog({
    required this.id,
    required this.name,
    required this.age,
  });

  // 将 Dog 转换为 Map。键必须与数据库中列的名称相对应。
  Map<String, Object?> toMap() {
    return {
      'id': id,
      'name': name,
      'age': age,
    };
  }

  // 实现 toString 以便在使用 print 语句时更容易查看有关每条狗的信息。
  @override
  String toString() {
    return 'Dog{id: $id, name: $name, age: $age}';
  }
}

(这段内容本身就是链接和说明,不需要翻译成中文。 如果需要翻译的是前面内容中用到的这些链接对应的页面内容,请提供这些页面的内容。)