在这里插入图片描述
在这里插入图片描述

概述

在Flutter应用开发中,当需要存储大量关系型数据时,Hive和SharedPreferences已经不能满足需求。sqflite是Flutter平台上的SQLite数据库插件,提供了完整的SQL关系型数据库功能,支持复杂查询、事务操作、索引优化等高级特性。

本文将详细介绍sqflite数据库的核心概念、使用方法、最佳实践以及在鸿蒙平台上的实现细节。

核心概念

什么是SQLite

SQLite是一个轻量级的嵌入式关系型数据库,具有以下特点:

  • 零配置:无需服务器,无需复杂配置
  • 自包含:数据库存储在单个文件中
  • 事务支持:支持ACID事务
  • SQL标准:支持SQL-92标准
  • 跨平台:支持Windows、Linux、macOS、Android、iOS等平台

sqflite的特点

sqflite是Flutter平台上的SQLite插件,具有以下特点:

  • 异步操作:所有数据库操作都是异步的
  • 事务支持:支持事务操作
  • 批处理:支持批量操作
  • 数据库版本管理:支持数据库升级和迁移
  • 跨平台:支持Android、iOS、鸿蒙等平台

核心组件

sqflite的核心组件包括:

  1. Database:数据库实例,用于执行SQL语句
  2. Transaction:事务对象,用于批量操作
  3. Batch:批处理对象,用于批量执行SQL语句
  4. OpenDatabaseOptions:数据库打开选项

基本使用

添加依赖

pubspec.yaml中添加依赖:

dependencies:
  sqflite: ^2.3.0
  path: ^1.8.3

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

打开数据库

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

Future<Database> openDatabase() async {
  final databasePath = await getDatabasesPath();
  final path = join(databasePath, 'example.db');
  
  return await openDatabase(
    path,
    version: 1,
    onCreate: (db, version) {
      // 创建表
      db.execute('''
        CREATE TABLE users (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT NOT NULL,
          email TEXT NOT NULL UNIQUE,
          age INTEGER,
          created_at TEXT NOT NULL
        )
      ''');
    },
  );
}

核心代码示例

代码示例1:数据库服务封装

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

class SqliteDatabaseService {
  static late Database _database;
  static const String _databaseName = 'app_database.db';
  static const int _databaseVersion = 1;

  static final SqliteDatabaseService _instance = SqliteDatabaseService._internal();
  factory SqliteDatabaseService() => _instance;
  SqliteDatabaseService._internal();

  Future<void> init() async {
    final databasePath = await getDatabasesPath();
    final path = join(databasePath, _databaseName);

    _database = await openDatabase(
      path,
      version: _databaseVersion,
      onCreate: _onCreate,
      onUpgrade: _onUpgrade,
      onOpen: _onOpen,
    );
  }

  Future<void> _onCreate(Database db, int version) async {
    await db.execute('''
      CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT NOT NULL UNIQUE,
        age INTEGER,
        is_active INTEGER DEFAULT 1,
        created_at TEXT NOT NULL
      )
    ''');

    await db.execute('''
      CREATE TABLE posts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        title TEXT NOT NULL,
        content TEXT,
        likes INTEGER DEFAULT 0,
        created_at TEXT NOT NULL,
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
      )
    ''');

    await db.execute('CREATE INDEX idx_posts_user_id ON posts(user_id)');
    await db.execute('CREATE INDEX idx_posts_created_at ON posts(created_at)');
  }

  Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
    if (oldVersion < 2) {
      await db.execute('ALTER TABLE users ADD COLUMN phone TEXT');
    }
    if (oldVersion < 3) {
      await db.execute('CREATE INDEX idx_users_email ON users(email)');
    }
  }

  Future<void> _onOpen(Database db) async {
    print('Database opened');
  }

  Future<int> insertUser(Map<String, dynamic> user) async {
    return await _database.insert('users', user);
  }

  Future<Map<String, dynamic>?> getUser(int id) async {
    final List<Map<String, dynamic>> results = await _database.query(
      'users',
      where: 'id = ?',
      whereArgs: [id],
    );
    return results.isNotEmpty ? results.first : null;
  }

  Future<List<Map<String, dynamic>>> getAllUsers() async {
    return await _database.query('users', orderBy: 'created_at DESC');
  }

  Future<int> updateUser(int id, Map<String, dynamic> user) async {
    return await _database.update(
      'users',
      user,
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  Future<int> deleteUser(int id) async {
    return await _database.delete(
      'users',
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  Future<List<Map<String, dynamic>>> getUserPosts(int userId) async {
    return await _database.query(
      'posts',
      where: 'user_id = ?',
      whereArgs: [userId],
      orderBy: 'created_at DESC',
    );
  }

  Future<void> close() async {
    await _database.close();
  }
}

代码说明

  1. 单例模式:确保全局唯一的数据库实例
  2. 数据库初始化:在init方法中打开数据库
  3. 表创建:在_onCreate中创建users和posts表
  4. 索引优化:为posts表创建索引
  5. 数据库升级:在_onUpgrade中处理版本升级
  6. CRUD操作:提供完整的增删改查方法

代码示例2:事务操作

class TransactionService {
  static Future<void> transferData(int fromUserId, int toUserId, double amount) async {
    final db = await SqliteDatabaseService()._database;
    
    await db.transaction((txn) async {
      final fromUser = await txn.query(
        'users',
        where: 'id = ?',
        whereArgs: [fromUserId],
      );
      
      if (fromUser.isEmpty) {
        throw Exception('Source user not found');
      }
      
      final fromBalance = fromUser.first['balance'] as double;
      if (fromBalance < amount) {
        throw Exception('Insufficient balance');
      }

      await txn.update(
        'users',
        {'balance': fromBalance - amount},
        where: 'id = ?',
        whereArgs: [fromUserId],
      );

      await txn.update(
        'users',
        {'balance': (await txn.query('users', where: 'id = ?', whereArgs: [toUserId]))
            .first['balance'] + amount},
        where: 'id = ?',
        whereArgs: [toUserId],
      );
    });
  }
}

代码说明

  1. 事务操作:使用transaction方法执行事务
  2. 原子性保证:事务中的所有操作要么全部成功,要么全部失败
  3. 异常处理:如果任何操作失败,事务会自动回滚
  4. 数据一致性:确保转账操作的数据一致性

代码示例3:批量操作

class BatchOperationService {
  static Future<void> batchInsertUsers(List<Map<String, dynamic>> users) async {
    final db = await SqliteDatabaseService()._database;
    final batch = db.batch();

    for (final user in users) {
      batch.insert('users', user);
    }

    final results = await batch.commit(noResult: true);
    print('Inserted ${users.length} users');
  }

  static Future<void> batchUpdatePosts(List<int> postIds, int likes) async {
    final db = await SqliteDatabaseService()._database;
    final batch = db.batch();

    for (final postId in postIds) {
      batch.update(
        'posts',
        {'likes': likes},
        where: 'id = ?',
        whereArgs: [postId],
      );
    }

    await batch.commit(noResult: true);
  }

  static Future<void> batchDeleteOldPosts(int daysToKeep) async {
    final db = await SqliteDatabaseService()._database;
    final cutoffDate = DateTime.now()
        .subtract(Duration(days: daysToKeep))
        .toIso8601String();

    await db.delete(
      'posts',
      where: 'created_at < ?',
      whereArgs: [cutoffDate],
    );
  }
}

代码说明

  1. 批量插入:使用batch对象批量插入数据
  2. 批量更新:使用batch对象批量更新数据
  3. noResult参数:设置为true可以提高性能
  4. 条件删除:根据条件批量删除数据

代码示例4:复杂查询

class QueryService {
  static Future<List<Map<String, dynamic>>> searchUsers(String keyword) async {
    final db = await SqliteDatabaseService()._database;
    return await db.query(
      'users',
      where: 'name LIKE ? OR email LIKE ?',
      whereArgs: ['%$keyword%', '%$keyword%'],
      orderBy: 'created_at DESC',
      limit: 20,
    );
  }

  static Future<List<Map<String, dynamic>>> getUsersWithPosts() async {
    final db = await SqliteDatabaseService()._database;
    return await db.rawQuery('''
      SELECT u.*, COUNT(p.id) as post_count
      FROM users u
      LEFT JOIN posts p ON u.id = p.user_id
      GROUP BY u.id
      ORDER BY post_count DESC
      LIMIT 10
    ''');
  }

  static Future<List<Map<String, dynamic>>> getRecentPosts(int limit) async {
    final db = await SqliteDatabaseService()._database;
    return await db.rawQuery('''
      SELECT p.*, u.name as author_name
      FROM posts p
      JOIN users u ON p.user_id = u.id
      ORDER BY p.created_at DESC
      LIMIT ?
    ''', [limit]);
  }

  static Future<Map<String, dynamic>> getStats() async {
    final db = await SqliteDatabaseService()._database;
    final userCount = await db.rawQuery('SELECT COUNT(*) as count FROM users');
    final postCount = await db.rawQuery('SELECT COUNT(*) as count FROM posts');
    final totalLikes = await db.rawQuery('SELECT SUM(likes) as total FROM posts');

    return {
      'user_count': userCount.first['count'] as int,
      'post_count': postCount.first['count'] as int,
      'total_likes': totalLikes.first['total'] as int? ?? 0,
    };
  }
}

代码说明

  1. 模糊搜索:使用LIKE操作符进行模糊搜索
  2. 联表查询:使用LEFT JOIN和JOIN进行联表查询
  3. 聚合函数:使用COUNT、SUM等聚合函数
  4. 参数化查询:使用?占位符防止SQL注入

代码示例5:数据库迁移

class MigrationService {
  static const String _versionKey = 'database_version';

  static Future<void> migrate() async {
    final db = await SqliteDatabaseService()._database;
    final currentVersion = await _getCurrentVersion(db);

    if (currentVersion < 1) {
      await _migrateToVersion1(db);
    }
    if (currentVersion < 2) {
      await _migrateToVersion2(db);
    }
    if (currentVersion < 3) {
      await _migrateToVersion3(db);
    }

    await _setCurrentVersion(db, 3);
  }

  static Future<int> _getCurrentVersion(Database db) async {
    try {
      final result = await db.query(_versionKey);
      return result.isNotEmpty ? result.first['version'] as int : 0;
    } catch (_) {
      return 0;
    }
  }

  static Future<void> _setCurrentVersion(Database db, int version) async {
    await db.delete(_versionKey);
    await db.insert(_versionKey, {'version': version});
  }

  static Future<void> _migrateToVersion1(Database db) async {
    await db.execute('''
      CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT NOT NULL UNIQUE,
        age INTEGER,
        created_at TEXT NOT NULL
      )
    ''');
  }

  static Future<void> _migrateToVersion2(Database db) async {
    await db.execute('ALTER TABLE users ADD COLUMN phone TEXT');
    await db.execute('ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1');
  }

  static Future<void> _migrateToVersion3(Database db) async {
    await db.execute('''
      CREATE TABLE posts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        title TEXT NOT NULL,
        content TEXT,
        likes INTEGER DEFAULT 0,
        created_at TEXT NOT NULL
      )
    ''');
    await db.execute('CREATE INDEX idx_posts_user_id ON posts(user_id)');
  }
}

代码说明

  1. 版本管理:使用_versionKey表存储当前数据库版本
  2. 增量迁移:根据当前版本执行相应的迁移脚本
  3. 数据安全:迁移过程中确保数据安全
  4. 兼容性:支持从任意旧版本升级到最新版本

高级特性

1. 数据库加密

可以使用sqflite_sqlcipher插件实现数据库加密:

import 'package:sqflite_sqlcipher/sqflite_sqlcipher.dart';

Future<Database> openEncryptedDatabase() async {
  final databasePath = await getDatabasesPath();
  final path = join(databasePath, 'encrypted.db');

  return await openDatabase(
    path,
    password: 'my_secret_password',
    version: 1,
    onCreate: (db, version) {
      db.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
    },
  );
}

2. 数据库备份与恢复

class DatabaseBackupService {
  static Future<void> backupDatabase(String backupPath) async {
    final db = await SqliteDatabaseService()._database;
    await db.close();

    final databasePath = await getDatabasesPath();
    final sourceFile = File(join(databasePath, 'app_database.db'));
    final backupFile = File(backupPath);

    await sourceFile.copy(backupPath);
    await SqliteDatabaseService().init();
  }

  static Future<void> restoreDatabase(String backupPath) async {
    final db = await SqliteDatabaseService()._database;
    await db.close();

    final databasePath = await getDatabasesPath();
    final targetFile = File(join(databasePath, 'app_database.db'));
    final backupFile = File(backupPath);

    await backupFile.copy(targetFile.path);
    await SqliteDatabaseService().init();
  }
}

3. 数据库性能优化

class PerformanceOptimization {
  static Future<void> optimizeDatabase() async {
    final db = await SqliteDatabaseService()._database;
    
    await db.execute('VACUUM');
    await db.execute('ANALYZE');
  }

  static Future<void> createIndexes() async {
    final db = await SqliteDatabaseService()._database;
    
    await db.execute('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_posts_user_id ON posts(user_id)');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_posts_created_at ON posts(created_at)');
  }
}

在鸿蒙平台的实现

鸿蒙平台适配

sqflite在鸿蒙平台上通过JNI调用鸿蒙平台的SQLite API实现。鸿蒙平台的SQLite与Android平台类似,但有一些差异:

  1. 存储位置:鸿蒙平台的数据库文件存储在应用沙盒目录下
  2. 权限配置:需要在module.json5中配置文件读写权限
  3. 性能优化:鸿蒙平台的SQLite性能与Android平台相当

存储位置

在鸿蒙平台上,sqflite数据存储在:

/data/data/<包名>/databases/

每个数据库对应一个文件,文件名格式为<数据库名>.db

鸿蒙平台注意事项

  1. 权限配置:在module.json5中配置ohos.permission.WRITE_USER_STORAGEohos.permission.READ_USER_STORAGE权限
  2. 数据库路径:使用getDatabasesPath()获取数据库目录
  3. 性能优化:避免在主线程执行耗时查询

性能对比

操作 sqflite Hive SharedPreferences
读取1000条数据 ~20ms ~5ms ~50ms
写入1000条数据 ~30ms ~10ms ~100ms
复杂查询 优秀 不支持
事务支持 优秀 中等 不支持
内存占用

最佳实践

1. 封装数据库操作

将数据库操作封装在Service层,避免直接在UI层使用SQL语句:

class UserRepository {
  final SqliteDatabaseService _dbService;

  UserRepository(this._dbService);

  Future<User> createUser(User user) async {
    final id = await _dbService.insertUser(user.toMap());
    return user.copyWith(id: id);
  }

  Future<User?> getUserById(int id) async {
    final map = await _dbService.getUser(id);
    return map != null ? User.fromMap(map) : null;
  }

  Future<List<User>> getAllUsers() async {
    final maps = await _dbService.getAllUsers();
    return maps.map((map) => User.fromMap(map)).toList();
  }
}

2. 使用参数化查询

避免SQL注入,使用参数化查询:

// 正确做法
await db.query(
  'users',
  where: 'email = ?',
  whereArgs: [email],
);

// 错误做法
await db.rawQuery('SELECT * FROM users WHERE email = "$email"');

3. 异步操作

所有数据库操作都是异步的,不要阻塞UI线程:

// 正确做法
FutureBuilder<List<User>>(
  future: userRepository.getAllUsers(),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return ListView.builder(
        itemCount: snapshot.data!.length,
        itemBuilder: (context, index) => ListTile(
          title: Text(snapshot.data![index].name),
        ),
      );
    }
    return const CircularProgressIndicator();
  },
);

4. 数据库关闭

在应用退出时关闭数据库:


void dispose() {
  SqliteDatabaseService().close();
  super.dispose();
}

5. 错误处理

对数据库操作进行错误处理:

try {
  await userRepository.createUser(user);
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('User created successfully')),
  );
} catch (e) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Error: $e')),
  );
}

常见问题

Q1: sqflite适合存储大量数据吗?

A:是的。sqflite基于SQLite,适合存储大量关系型数据。

Q2: sqflite支持并发访问吗?

A:sqflite的Database对象不是线程安全的,需要在单个Isolate中使用。

Q3: 如何处理数据库升级?

A:使用onUpgrade回调处理数据库升级。

Q4: sqflite支持外键约束吗?

A:是的,sqflite支持外键约束,但需要在创建表时显式声明。

Q5: 如何提高数据库性能?

A:可以通过创建索引、使用事务、批量操作等方式提高性能。

总结

sqflite是Flutter应用中强大的关系型数据库方案,特别适合存储大量关系型数据和复杂查询场景。它具有完整的SQL功能、事务支持、索引优化等高级特性,API简单直观,易于上手。在鸿蒙平台上,sqflite同样可以正常工作,无需额外配置。

选择合适的数据库方案需要根据数据量、复杂度和性能需求来决定:

方案 适用场景 数据量 复杂度
SharedPreferences 用户配置、设置
Hive 中等数据量、复杂对象
sqflite 大量数据、关系型数据

希望本文能帮助你更好地理解和使用sqflite在Flutter应用中进行本地数据持久化。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐