Flutter数据库管理插件dsqlite的使用

Flutter数据库管理插件dsqlite的使用

简介

dsqlite 包提供了一个 package:database_sql 的实现驱动。sql 包必须与 package:database_sql 包一起使用。

该包提供了 package:database_sql 包的一个完整实现。当在网页浏览器上运行时,它使用由 emscripten 编译自 sqlite3 源代码的 WebAssembly API。所有主要的现代浏览器如 Chrome、Firefox 和 Safari 都支持。

重要事项

网页版在此包中使用的是通过 emscripten 运行输出的 WebAssembly。尽管它可以工作,但性能相比原生的 IndexedDB JavaScript 要慢很多。此外,所有 SQLite 数据都会加载到内存中,导致应用程序消耗大量的内存。另外,emscripten 没有提供官方文档来实现与浏览器本地文件系统 API 结合的文件系统 API,这可能会显著减少虚拟文件系统的内存需求。有关 emscripten 的问题跟踪请求和更好的 API 来实现自定义文件系统 API:

下一步

  • 探索可能性通过或不通过 15041 实现 VFS。
  • 探索可能性通过模块或虚拟表实现将数据重定向到 IndexedDB。缺点是当使用虚拟表时,不是所有的 SQLite 特性都受支持。
  • 将 SQLite 功能移动到 Web Worker 或 Service Worker 而不是主线程。

使用方法

pubspec.yaml 中添加 package:database_sqldsqlite

dependencies:
  database_sql: 0.0.1
  dsqlite: 0.0.1

以下是一个完整的示例:

import 'package:database_sql/database_sql.dart' as sqldb;
import 'package:dsqlite/dsqlite.dart' as dsqlite;

void main() async {
  // 注册驱动(应该在不同的隔离进程中注册)
  sqldb.registerDriver(
    'sqlite3',
    await dsqlite.Driver.initialize(
      // 在 iOS 上无需提供路径。
      // 在 Web 上,与 iOS 相同,Web 需要 index.html 来加载 JavaScript 文件和 WebAssembly
      path: '/* 路径到动态库 */',
      // 连接钩子是可选的,但在需要注册钩子事件或创建自定义函数时非常有用
      connectHook: (driver, db, ds) {
        /* 你的初始化代码,例如注册函数、钩子事件 */
      },
    ),
  );

  // 确保即使发生异常,数据库连接也能正确关闭
  await sqldb.protect('sqlite3', dsqlite.DataSource('test.db'), block: (db) async {
    await db.exec('CREATE TABLE sample(id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT);');
    final changes = await db.exec("INSERT INTO sample(text) VALUES('sqlite3-driver')");
    // 应该打印 'Last ID: 1, affected: 1'
    print('Last ID: ${changes.lastInsertId}, affected: ${changes.rowsAffected}');
    
    // 确保查询结果即使在异常情况下也能正确关闭
    await db.protectQuery<sqldb.Row>('SELECT * FROM sample;', block: (rows) async {
      while (rows.moveNext()) {
        // 应该打印 'ID: 1, text: sqlite3-driver'
        print('ID: ${rows.current.intValueBy('id')}, text: ${rows.current.stringValueBy('text')}');
      }
    });
  });
}

编译 WebAssembly

sqlite3 源代码使用 emscripten 编译 WebAssembly。在你能够编译 WebAssembly 之前,必须安装并配置好 emscripten 并将其添加到你的 PATH 环境变量中。有关如何安装 emscripten 的详细信息,请参阅 Getting Started

安装好 emscripten 后,运行以下命令以生成 WebAssembly 文件。默认情况下,它使用包内预定义的构建配置。这些设置会生成多个格式为 ASM 和 WASM 的输出,并且包括对 JavaScript bigint 支持和不支持的版本。

dart run dsqlite -b -d --release 2021:3.36.00 -o /* 存储生成文件的目录 */

如果要禁用多构建或包含自己的优化,则可以提供自定义构建配置。在你的根存储库中创建一个名为 sqlite_webassembly_build.yaml 的配置文件。

# 这是在网页浏览器中运行时 dsqlite 使用的最小运行时导出 API
# 如果你的应用不使用 UTF-16,则可以省略 `UTF16ToString`、`stringToUTF16` 和 `lengthBytesUTF16` API。同样的二进制文件或 Blob 可以省略 `writeArrayToMemory`。
# 如果你的应用不需要 sqlite3 API,如创建函数或回调,则可以省略 `addFunction` 和 `removeFunction`。这样可以将生成的 JavaScript 大小稍微减小一些。
runtime:
  - 'cwrap'
  - 'setValue'
  - 'getValue'
  - 'UTF8ToString'
  - 'stringToUTF8'
  - 'UTF16ToString'
  - 'stringToUTF16'
  - 'writeArrayToMemory'
  - 'lengthBytesUTF8'
  - 'lengthBytesUTF16'
  - 'addFunction'
  - 'removeFunction'

emitBitInt: true # true 用于生成同时支持和不支持 bigint 的版本

# 如果未提供,将使用此包内的配置文件中的 cflag。
# cflag:
#   - '-O2'
#   - '-DSQLITE_THREADSAFE=0' # JavaScript 是单线程的

# 如果未提供,将使用此包内的配置文件中的 emflag。
# emflag:

# 如果未提供,将使用此包内的配置文件中的 release。
# 这是 emcc 用于发布构建的标志
# release:

# 如果未提供,将使用此包内的配置文件中的 debug。
# 这是 emcc 用于调试构建的标志
# debug:

# 提供 wasm 键以指示生成的输出为 WebAssembly wasm 格式
# 必须提供 emcc 标志
# wasm:
#   - '-s ALLOW_MEMORY_GROWTH=1'

# 提供 asm 键以指示生成的输出为 asm.js 格式
# 必须提供 emcc 标志
# asm:
#   - '-s ALLOW_MEMORY_GROWTH=1'

测试

在运行测试之前,需要下载 SQLite 源代码并进行本地构建,因此你必须安装 emscripten 和必要的工具来从源代码构建 SQLite。

测试代码期望动态库的位置为:

# Windows 平台
sqlite/latest/sqlite3.dll
# Linux,此库由 sqlite3 源代码 Makefile 自动生成。
sqlite/latest/.libs/libsqlite3.so
# macOS,此库由 sqlite3 源代码 Makefile 自动生成。
sqlite/latest/.libs/libsqlite3.dylib

对于 Windows 用户,需要手动构建 SQLite3 库 sqlite3.dll。请遵循 sqlite 官方文档的描述 这里

对于其他平台的用户,使用 make 构建和运行测试。Windows 用户可以使用 powershell 或其他命令行应用程序来运行 makefile。

make requisition
# 测试浏览器(仅调试 WebAssembly)和 VM
make dsqlite_test_local
# 全面测试浏览器(调试和发布)以及 VM
make dsqlite_test

更多关于Flutter数据库管理插件dsqlite的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter数据库管理插件dsqlite的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,如果你需要使用SQLite数据库进行本地数据存储,可以使用 sqflite 插件。sqflite 是一个流行的Flutter插件,它提供了对SQLite数据库的访问和管理功能。以下是使用 sqflite 插件的基本步骤:

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 sqflitepath 依赖:

dependencies:
  flutter:
    sdk: flutter
  sqflite: ^2.0.0+4
  path: ^1.8.0

然后运行 flutter pub get 来安装依赖。

2. 创建数据库

接下来,你可以创建一个数据库并定义表结构。以下是一个简单的例子:

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

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  static Database? _database;

  factory DatabaseHelper() {
    return _instance;
  }

  DatabaseHelper._internal();

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  Future<Database> _initDatabase() async {
    String path = join(await getDatabasesPath(), 'my_database.db');
    return await openDatabase(
      path,
      version: 1,
      onCreate: _onCreate,
    );
  }

  Future<void> _onCreate(Database db, int version) async {
    await db.execute(
      'CREATE TABLE my_table(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
    );
  }
}

3. 插入数据

你可以使用 insert 方法向数据库中插入数据:

Future<void> insertData(String name, int age) async {
  final db = await DatabaseHelper().database;
  await db.insert(
    'my_table',
    {'name': name, 'age': age},
    conflictAlgorithm: ConflictAlgorithm.replace,
  );
}

4. 查询数据

你可以使用 query 方法从数据库中查询数据:

Future<List<Map<String, dynamic>>> getData() async {
  final db = await DatabaseHelper().database;
  return await db.query('my_table');
}

5. 更新数据

你可以使用 update 方法更新数据库中的数据:

Future<void> updateData(int id, String name, int age) async {
  final db = await DatabaseHelper().database;
  await db.update(
    'my_table',
    {'name': name, 'age': age},
    where: 'id = ?',
    whereArgs: [id],
  );
}

6. 删除数据

你可以使用 delete 方法删除数据库中的数据:

Future<void> deleteData(int id) async {
  final db = await DatabaseHelper().database;
  await db.delete(
    'my_table',
    where: 'id = ?',
    whereArgs: [id],
  );
}

7. 关闭数据库

在应用程序退出时,你可以关闭数据库以释放资源:

Future<void> closeDatabase() async {
  final db = await DatabaseHelper().database;
  await db.close();
}

示例代码

以下是一个完整的示例代码,展示了如何使用 sqflite 插件进行数据库操作:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('SQLite Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () async {
                  await DatabaseHelper().insertData('Alice', 30);
                  print('Data inserted');
                },
                child: Text('Insert Data'),
              ),
              ElevatedButton(
                onPressed: () async {
                  List<Map<String, dynamic>> data = await DatabaseHelper().getData();
                  print('Data: $data');
                },
                child: Text('Query Data'),
              ),
              ElevatedButton(
                onPressed: () async {
                  await DatabaseHelper().updateData(1, 'Alice Smith', 31);
                  print('Data updated');
                },
                child: Text('Update Data'),
              ),
              ElevatedButton(
                onPressed: () async {
                  await DatabaseHelper().deleteData(1);
                  print('Data deleted');
                },
                child: Text('Delete Data'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  static Database? _database;

  factory DatabaseHelper() {
    return _instance;
  }

  DatabaseHelper._internal();

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  Future<Database> _initDatabase() async {
    String path = join(await getDatabasesPath(), 'my_database.db');
    return await openDatabase(
      path,
      version: 1,
      onCreate: _onCreate,
    );
  }

  Future<void> _onCreate(Database db, int version) async {
    await db.execute(
      'CREATE TABLE my_table(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
    );
  }

  Future<void> insertData(String name, int age) async {
    final db = await database;
    await db.insert(
      'my_table',
      {'name': name, 'age': age},
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  Future<List<Map<String, dynamic>>> getData() async {
    final db = await database;
    return await db.query('my_table');
  }

  Future<void> updateData(int id, String name, int age) async {
    final db = await database;
    await db.update(
      'my_table',
      {'name': name, 'age': age},
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  Future<void> deleteData(int id) async {
    final db = await database;
    await db.delete(
      'my_table',
      where: 'id = ?',
      whereArgs: [id],
    );
  }
}
回到顶部