使用 SQLite 持久化数据
如何使用 SQLite 存储和检索数据。
如果您编写的应用程序需要在本地设备上持久化和查询大量数据,请考虑使用数据库,而不是本地文件或键值存储。通常情况下,与其他本地持久化方案相比,数据库提供更快的插入、更新和查询速度。
Flutter 应用程序可以通过 pub.dev 上提供的 sqflite 插件使用 SQLite 数据库。本篇指南演示了使用 sqflite 对各种 Dog 信息进行插入、读取、更新和删除的基本操作。
如果您是 SQLite 和 SQL 语句的新手,请在阅读本指南前通过 SQLite 教程了解基础知识。
本示例将采取以下步骤
- 添加依赖。
- 定义
Dog数据模型。 - 打开数据库。
- 创建
dogs表。 - 将
Dog插入数据库。 - 获取 Dog 列表。
- 更新数据库中的
Dog。 - 从数据库中删除
Dog。
1. 添加依赖
#要使用 SQLite 数据库,请导入 sqflite 和 path 包。
sqflite包提供了与 SQLite 数据库交互的类和函数。path包提供了定义数据库在磁盘上存储位置的函数。
要将这些包添加为依赖项,请运行 flutter pub add
flutter pub add sqflite path
确保在你所工作的文件中导入这些包。
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
2. 定义 Dog 数据模型
#在创建用于存储 Dog 信息的表之前,花点时间定义需要存储的数据。对于本示例,定义一个包含三部分数据的 Dog 类:唯一的 id、name 和每只狗的 age。
class Dog {
final int id;
final String name;
final int age;
const Dog({required this.id, required this.name, required this.age});
}
3. 打开数据库
#在读取和写入数据库数据之前,需要打开一个到数据库的连接。这涉及两个步骤:
- 使用
sqflite包中的getDatabasesPath()函数,结合path包中的join函数,定义数据库文件的路径。 - 使用
sqflite中的openDatabase()函数打开数据库。
// Avoid errors caused by flutter upgrade.
// Importing 'package:flutter/widgets.dart' is required.
WidgetsFlutterBinding.ensureInitialized();
// Open the database and store the reference.
final database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'doggie_database.db'),
);
4. 创建 dogs 表
#
接下来,创建一个表来存储有关各种 Dog 的信息。对于本示例,创建一个名为 dogs 的表,定义可以存储的数据。每只 Dog 都包含 id、name 和 age。因此,它们在 dogs 表中表示为三列。
id是一个 Dartint,存储为INTEGERSQLite 数据类型。通常将id作为表的主键,以提高查询和更新速度,这是一个良好的实践。name是一个 DartString,存储为TEXTSQLite 数据类型。age也是一个 Dartint,存储为INTEGER数据类型。
有关 SQLite 数据库中可存储数据类型的更多信息,请参阅 SQLite 官方数据类型文档。
final database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'doggie_database.db'),
// When the database is first created, create a table to store dogs.
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
5. 将 Dog 插入数据库
#现在你已经拥有了一个带有存储各种狗信息表的数据库,是时候读取和写入数据了。
首先,将 Dog 插入 dogs 表。这涉及两个步骤:
- 将
Dog转换为Map - 使用
insert()方法将Map存储到dogs表中。
class Dog {
final int id;
final String name;
final int age;
Dog({required this.id, required this.name, required this.age});
// Convert a Dog into a Map. The keys must correspond to the names of the
// columns in the database.
Map<String, Object?> toMap() {
return {'id': id, 'name': name, 'age': age};
}
// Implement toString to make it easier to see information about
// each dog when using the print statement.
@override
String toString() {
return 'Dog{id: $id, name: $name, age: $age}';
}
}
// Define a function that inserts dogs into the database
Future<void> insertDog(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Insert the Dog into the correct table. You might also specify the
// `conflictAlgorithm` to use in case the same dog is inserted twice.
//
// In this case, replace any previous data.
await db.insert(
'dogs',
dog.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// Create a Dog and add it to the dogs table
var fido = Dog(id: 0, name: 'Fido', age: 35);
await insertDog(fido);
6. 获取 Dog 列表
#现在 Dog 已存储在数据库中,查询数据库以获取特定狗或所有狗的列表。这涉及两个步骤:
- 对
dogs表运行query。这将返回一个List<Map>。 - 将
List<Map>转换为List<Dog>。
// A method that retrieves all the dogs from the dogs table.
Future<List<Dog>> dogs() async {
// Get a reference to the database.
final db = await database;
// Query the table for all the dogs.
final List<Map<String, Object?>> dogMaps = await db.query('dogs');
// Convert the list of each dog's fields into a list of `Dog` objects.
return [
for (final {'id': id as int, 'name': name as String, 'age': age as int}
in dogMaps)
Dog(id: id, name: name, age: age),
];
}
// Now, use the method above to retrieve all the dogs.
print(await dogs()); // Prints a list that include Fido.
7. 更新数据库中的 Dog
#
将信息插入数据库后,你可能希望稍后更新这些信息。你可以使用 sqflite 库中的 update() 方法来实现。
这涉及两个步骤:
- 将 Dog 转换为 Map。
- 使用
where子句以确保更新正确的 Dog。
Future<void> updateDog(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'dogs',
dog.toMap(),
// Ensure that the Dog has a matching id.
where: 'id = ?',
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [dog.id],
);
}
// Update Fido's age and save it to the database.
fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7);
await updateDog(fido);
// Print the updated results.
print(await dogs()); // Prints Fido with age 42.
8. 从数据库中删除 Dog
#
除了插入和更新 Dog 信息外,你还可以从数据库中删除狗。要删除数据,请使用 sqflite 库中的 delete() 方法。
在本节中,创建一个接收 id 的函数,并从数据库中删除匹配该 id 的狗。为了使其正常工作,必须提供一个 where 子句来限制要删除的记录。
Future<void> deleteDog(int id) async {
// Get a reference to the database.
final db = await database;
// Remove the Dog from the database.
await db.delete(
'dogs',
// Use a `where` clause to delete a specific dog.
where: 'id = ?',
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [id],
);
}
示例
#运行示例:
- 创建一个新的 Flutter 项目。
- 将
sqflite和path包添加到你的pubspec.yaml中。 - 将以下代码粘贴到一个名为
lib/db_test.dart的新文件中。 - 使用
flutter run lib/db_test.dart运行代码。
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
void main() async {
// Avoid errors caused by flutter upgrade.
// Importing 'package:flutter/widgets.dart' is required.
WidgetsFlutterBinding.ensureInitialized();
// Open the database and store the reference.
final database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'doggie_database.db'),
// When the database is first created, create a table to store dogs.
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
// Define a function that inserts dogs into the database
Future<void> insertDog(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Insert the Dog into the correct table. You might also specify the
// `conflictAlgorithm` to use in case the same dog is inserted twice.
//
// In this case, replace any previous data.
await db.insert(
'dogs',
dog.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// A method that retrieves all the dogs from the dogs table.
Future<List<Dog>> dogs() async {
// Get a reference to the database.
final db = await database;
// Query the table for all the dogs.
final List<Map<String, Object?>> dogMaps = await db.query('dogs');
// Convert the list of each dog's fields into a list of `Dog` objects.
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 {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'dogs',
dog.toMap(),
// Ensure that the Dog has a matching id.
where: 'id = ?',
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [dog.id],
);
}
Future<void> deleteDog(int id) async {
// Get a reference to the database.
final db = await database;
// Remove the Dog from the database.
await db.delete(
'dogs',
// Use a `where` clause to delete a specific dog.
where: 'id = ?',
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [id],
);
}
// Create a Dog and add it to the dogs table
var fido = Dog(id: 0, name: 'Fido', age: 35);
await insertDog(fido);
// Now, use the method above to retrieve all the dogs.
print(await dogs()); // Prints a list that include Fido.
// Update Fido's age and save it to the database.
fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7);
await updateDog(fido);
// Print the updated results.
print(await dogs()); // Prints Fido with age 42.
// Delete Fido from the database.
await deleteDog(fido.id);
// Print the list of dogs (empty).
print(await dogs());
}
class Dog {
final int id;
final String name;
final int age;
Dog({required this.id, required this.name, required this.age});
// Convert a Dog into a Map. The keys must correspond to the names of the
// columns in the database.
Map<String, Object?> toMap() {
return {'id': id, 'name': name, 'age': age};
}
// Implement toString to make it easier to see information about
// each dog when using the print statement.
@override
String toString() {
return 'Dog{id: $id, name: $name, age: $age}';
}
}
使用表之间的关系
#在实际应用程序中,你通常需要建模表之间的关系。
例如,与其将所有数据存储在一个表中,不如将相关数据拆分到多个表中,并使用外键将它们连接起来。
CREATE TABLE breeds(
id INTEGER PRIMARY KEY,
name TEXT
);
You can execute these statements using the `db.execute()` method from the `sqflite` package.
CREATE TABLE dogs(
id INTEGER PRIMARY KEY,
name TEXT,
age INTEGER,
breed_id INTEGER,
FOREIGN KEY (breed_id) REFERENCES breeds(id)
);
在此示例中,每只狗通过 breed_id 字段与一个品种关联。这使你能够更有效地组织数据并避免重复。