跳到主內容

使用 SQLite 持久化資料

如何使用 SQLite 儲存和檢索資料。

如果您正在編寫需要在本地裝置上持久化和查詢大量資料的應用,請考慮使用資料庫而不是本地檔案或鍵值儲存。通常,與其他本地持久化解決方案相比,資料庫提供更快的插入、更新和查詢速度。

Flutter 應用可以透過 pub.dev 上提供的 sqflite 外掛使用 SQLite 資料庫。本教程演示了使用 sqflite 插入、讀取、更新和刪除有關各種 Dog 的資料的基本知識。

如果您不熟悉 SQLite 和 SQL 語句,請檢視 SQLite 教程,在完成本教程之前學習基礎知識。

本示例將採取以下步驟

  1. 新增依賴項。
  2. 定義 Dog 資料模型。
  3. 開啟資料庫。
  4. 建立 dogs 表。
  5. Dog 插入資料庫。
  6. 檢索 dogs 列表。
  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 資訊的表之前,花幾分鐘時間定義需要儲存的資料。對於本示例,定義一個 Dog 類,其中包含三條資料:一個唯一的 idname 和每個 Dog 的 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
// 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 包含一個 idnameage。因此,這些在 dogs 表中表示為三個列。

  1. id 是一個 Dart int,並存儲為 SQLite 資料型別 INTEGER。將 id 作為表的唯一識別符號也是一個好習慣,以提高查詢和更新速度。
  2. name 是一個 Dart String,並存儲為 SQLite 資料型別 TEXT
  3. age 也是一個 Dart int,並存儲為 INTEGER 資料型別。

有關可以在 SQLite 資料庫中儲存的可用資料型別的更多資訊,請參閱 官方 SQLite 資料型別文件

dart
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 的資訊的表的資料庫,現在可以開始讀取和寫入資料了。

首先,將 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});

  // 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}';
  }
}
dart
// 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,
  );
}
dart
// 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 儲存在資料庫中,查詢資料庫以獲取特定的 Dog 或所有 Dog 的列表。這涉及兩個步驟

  1. dogs 表執行 query。這將返回一個 List<Map>
  2. List<Map> 轉換為 List<Dog>
dart
// 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),
  ];
}
dart
// Now, use the method above to retrieve all the dogs.
print(await dogs()); // Prints a list that include Fido.

7. 更新資料庫中的 Dog

#

在將資訊插入資料庫後,您可能希望稍後更新該資訊。您可以使用 sqflite 庫中的 update() 方法來執行此操作。

這涉及兩個步驟

  1. 將 Dog 轉換為 Map。
  2. 使用 where 子句以確保更新正確的 Dog。
dart
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],
  );
}
dart
// 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 的資訊外,還可以從資料庫中刪除 Dog。要刪除資料,請使用 sqflite 庫中的 delete() 方法。

在本節中,建立一個函式,該函式接受一個 id 並從資料庫中刪除具有匹配 id 的 Dog。為了使這起作用,您必須提供一個 where 子句來限制被刪除的記錄。

dart
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],
  );
}

示例

#

要執行示例

  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 {
  // 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}';
  }
}