跳到主內容

使用 SQLite 持久化資料

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

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

Flutter 應用程式可以透過 pub.dev 上提供的 sqflite 外掛使用 SQLite 資料庫。本篇指南演示了使用 sqflite 對各種 Dog 資訊進行插入、讀取、更新和刪除的基本操作。

如果您是 SQLite 和 SQL 語句的新手,請在閱讀本指南前透過 SQLite 教程瞭解基礎知識。

本示例將採取以下步驟

  1. 新增依賴。
  2. 定義 Dog 資料模型。
  3. 開啟資料庫。
  4. 建立 dogs 表。
  5. Dog 插入資料庫。
  6. 獲取 Dog 列表。
  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 和每隻狗的 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,儲存為 INTEGER SQLite 資料型別。通常將 id 作為表的主鍵,以提高查詢和更新速度,這是一個良好的實踐。
  2. name 是一個 Dart String,儲存為 TEXT SQLite 資料型別。
  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 插入 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 已儲存在資料庫中,查詢資料庫以獲取特定狗或所有狗的列表。這涉及兩個步驟:

  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 資訊外,你還可以從資料庫中刪除狗。要刪除資料,請使用 sqflite 庫中的 delete() 方法。

在本節中,建立一個接收 id 的函式,並從資料庫中刪除匹配該 id 的狗。為了使其正常工作,必須提供一個 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}';
  }
}

使用表之間的關係

#

在實際應用程式中,你通常需要建模表之間的關係。

例如,與其將所有資料儲存在一個表中,不如將相關資料拆分到多個表中,並使用外部索引鍵將它們連線起來。

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