使用 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 欄位與一個品種關聯。這使你能夠更有效地組織資料並避免重複。