## 🎯 里程碑完成:数据层架构建设 ### ✅ 数据持久化实现 - Hive数据库完整集成 - 依赖配置、初始化、TypeAdapter注册 - BookRepository数据访问层 - 完整CRUD操作实现 - 自动代码生成 - build_runner + hive_generator集成 - 数据持久化验证 - 应用启动时自动测试所有功能 ### 🏗️ 架构组件 - DatabaseService - 单例模式数据库管理服务 - BookRepository - Repository模式数据访问抽象层 - TypeAdapter - 自动生成对象序列化适配器 - 错误处理机制 - 完善的异常捕获和日志记录 ### 📊 代码成果 - 新增2个服务类文件 (database_service.dart, book_repository.dart) - 自动生成1个TypeAdapter文件 (book.g.dart) - 更新4个数据模型文件 (添加Hive注解) - 完善main.dart集成测试验证 - 新增1篇Hive详细教程文档 (06_Hive数据库数据持久化详解.md) ### 🧪 测试验证 - 数据库初始化测试 ✅ - CRUD操作完整测试 ✅ - 数据持久化验证 ✅ - TypeAdapter序列化测试 ✅ - 错误处理机制测试 ✅ ### 📚 文档完善 - 更新项目主文档 (CLAUDE.md) - 完整进度和成果统计 - 更新学习阶段总结 (05_数据模型设计阶段总结.md) - 新增Hive使用详解 (06_Hive数据库数据持久化详解.md) - 详细的代码示例和最佳实践指南 🚀 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
973 lines
25 KiB
Markdown
973 lines
25 KiB
Markdown
# Hive数据库数据持久化详解
|
||
|
||
## 📚 章节概述
|
||
**学习目标:** 掌握Flutter中的数据持久化,理解Hive数据库的原理和使用方法
|
||
**掌握程度:** 核心知识点 ⭐⭐⭐⭐⭐
|
||
**完成状态:** ✅ 已完成
|
||
|
||
---
|
||
|
||
## 🎯 核心概念理解
|
||
|
||
### 1️⃣ 什么是数据持久化?
|
||
|
||
**数据持久化** = 数据保存 + 长期存储
|
||
|
||
**生活中的比喻:**
|
||
- 📱 微信聊天记录 - 关闭APP后重新打开,聊天记录还在
|
||
- 📞 手机通讯录 - 重启手机后联系人信息不丢失
|
||
- 🎮 游戏进度 - 退出游戏下次继续,进度保存着
|
||
- 📚 我们的电子书库 - 退出Readful应用后,书籍信息还在
|
||
|
||
### 2️⃣ 为什么需要数据持久化?
|
||
|
||
**问题:** 如果没有数据持久化
|
||
- 用户添加的书籍 → 关闭应用就丢失了 😱
|
||
- 阅读进度 → 重新打开要从头开始 😭
|
||
- 书签和高亮 → 全部消失 😰
|
||
|
||
**解决方案:** 数据持久化
|
||
- ✅ 数据永久保存(除非用户删除)
|
||
- ✅ 应用重启后数据依然存在
|
||
- ✅ 用户体验持续和连贯
|
||
|
||
### 3️⃣ 为什么选择Hive数据库?
|
||
|
||
**Hive = 轻量级 + 快速 + 简单**
|
||
|
||
**🚀 优势对比:**
|
||
|
||
| 特性 | Hive | SQLite | SharedPreferences |
|
||
|------|------|--------|-------------------|
|
||
| **速度** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||
| **易用性** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
|
||
| **对象支持** | ✅ 直接存储对象 | ❌ 需要SQL语句 | ❌ 只支持基本类型 |
|
||
| **离线能力** | ✅ 完全离线 | ✅ 完全离线 | ✅ 完全离线 |
|
||
| **类型安全** | ✅ 编译时检查 | ⚠️ 运行时错误 | ⚠️ 运行时错误 |
|
||
|
||
---
|
||
|
||
## 🏗️ 架构设计详解
|
||
|
||
### 架构层次图
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ 用户界面 (UI) │
|
||
│ (List, Detail等页面) │
|
||
└─────────────────┬───────────────────┘
|
||
│
|
||
┌─────────────────▼───────────────────┐
|
||
│ BookRepository │
|
||
│ (数据访问层) │
|
||
│ getAllBooks(), addBook()等 │
|
||
└─────────────────┬───────────────────┘
|
||
│
|
||
┌─────────────────▼───────────────────┐
|
||
│ DatabaseService │
|
||
│ (数据库服务层) │
|
||
│ 初始化、注册、打开Box等 │
|
||
└─────────────────┬───────────────────┘
|
||
│
|
||
┌─────────────────▼───────────────────┐
|
||
│ Hive数据库 │
|
||
│ (物理存储层) │
|
||
│ books.hive文件存储 │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 🛠️ Hive基础使用方法
|
||
|
||
### 📦 Hive安装和配置
|
||
|
||
#### 1️⃣ 依赖配置
|
||
```yaml
|
||
# pubspec.yaml
|
||
dependencies:
|
||
hive: ^2.2.3 # Hive核心库
|
||
hive_flutter: ^1.1.0 # Flutter集成
|
||
path_provider: ^2.0.14 # 文件路径管理
|
||
|
||
dev_dependencies:
|
||
hive_generator: ^2.0.0 # TypeAdapter生成器
|
||
build_runner: ^2.4.6 # 代码生成工具
|
||
```
|
||
|
||
#### 2️⃣ 安装依赖
|
||
```bash
|
||
flutter pub get
|
||
```
|
||
|
||
### 🔧 Hive基础操作
|
||
|
||
#### 1️⃣ 数据库初始化
|
||
```dart
|
||
import 'package:hive_flutter/hive_flutter.dart';
|
||
import 'package:path_provider/path_provider.dart';
|
||
|
||
Future<void> initHive() async {
|
||
// 获取应用文档目录
|
||
final appDocumentDir = await getApplicationDocumentsDirectory();
|
||
|
||
// 初始化Hive
|
||
await Hive.initFlutter(appDocumentDir.path);
|
||
|
||
print('✅ Hive初始化完成');
|
||
}
|
||
```
|
||
|
||
#### 2️⃣ 打开和关闭Box
|
||
```dart
|
||
// 打开Box(数据容器)
|
||
Box<String> settingsBox = await Hive.openBox<String>('settings');
|
||
Box<Book> booksBox = await Hive.openBox<Book>('books');
|
||
|
||
// 检查Box是否打开
|
||
bool isOpen = Hive.isBoxOpen('books');
|
||
|
||
// 获取已打开的Box
|
||
Box<String> myBox = Hive.box('settings');
|
||
|
||
// 关闭单个Box
|
||
await settingsBox.close();
|
||
|
||
// 关闭所有Box
|
||
await Hive.close();
|
||
```
|
||
|
||
#### 3️⃣ 基本CRUD操作
|
||
```dart
|
||
// 📝 CREATE - 添加数据
|
||
await settingsBox.put('theme', 'dark');
|
||
await settingsBox.put('fontSize', 16.0);
|
||
|
||
// 📖 READ - 读取数据
|
||
String? theme = settingsBox.get('theme'); // 'dark'
|
||
double fontSize = settingsBox.get('fontSize', defaultValue: 14.0); // 16.0
|
||
|
||
// 获取所有键值对
|
||
Map<String, dynamic> allSettings = settingsBox.toMap();
|
||
|
||
// 获取所有值
|
||
List<String> allValues = settingsBox.values.toList();
|
||
|
||
// 获取所有键
|
||
List<String> allKeys = settingsBox.keys.toList();
|
||
|
||
// ✏️ UPDATE - 更新数据
|
||
await settingsBox.put('theme', 'light'); // 更新已存在的键
|
||
|
||
// 🗑️ DELETE - 删除数据
|
||
await settingsBox.delete('theme');
|
||
await settingsBox.deleteAll(['theme', 'fontSize']);
|
||
|
||
// 清空整个Box
|
||
await settingsBox.clear();
|
||
```
|
||
|
||
### 🎯 Hive高级操作
|
||
|
||
#### 1️⃣ 条件查询
|
||
```dart
|
||
// 查询所有key以'user_'开头的项目
|
||
Iterable<String> userKeys = settingsBox.keys.where((key) => key.startsWith('user_'));
|
||
|
||
// 查询所有值大于10的项目
|
||
Iterable<double> largeValues = settingsBox.values.whereType<double>()
|
||
.where((value) => value > 10);
|
||
|
||
// 查找特定条件的键值对
|
||
Map<String, dynamic> filteredData = {};
|
||
for (var entry in settingsBox.toMap().entries) {
|
||
if (entry.key.toString().contains('important')) {
|
||
filteredData[entry.key] = entry.value;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2️⃣ 数据监听
|
||
```dart
|
||
// 监听数据变化
|
||
settingsBox.listenable().addListener(() {
|
||
print('Settings box数据发生变化');
|
||
// 在这里可以更新UI或执行其他操作
|
||
});
|
||
|
||
// 监听特定键的变化
|
||
settingsBox.watch(key: 'theme').listen((event) {
|
||
print('主题设置改变: ${event.value}');
|
||
// 可以在这里切换UI主题
|
||
});
|
||
```
|
||
|
||
#### 3️⃣ 批量操作
|
||
```dart
|
||
// 批量添加数据
|
||
await settingsBox.putAll({
|
||
'theme': 'dark',
|
||
'fontSize': 16.0,
|
||
'language': 'zh_CN',
|
||
'autoSave': true,
|
||
});
|
||
|
||
// 批量删除数据
|
||
List<String> keysToDelete = ['temp1', 'temp2', 'temp3'];
|
||
await settingsBox.deleteAll(keysToDelete);
|
||
```
|
||
|
||
### 🔍 Hive数据类型支持
|
||
|
||
#### 1️⃣ 原生支持的数据类型
|
||
```dart
|
||
// ✅ 基本类型
|
||
Box<String> stringBox = await Hive.openBox<String>('strings');
|
||
Box<int> intBox = await Hive.openBox<int>('integers');
|
||
Box<double> doubleBox = await Hive.openBox<double>('doubles');
|
||
Box<bool> boolBox = await Hive.openBox<bool>('booleans');
|
||
Box<DateTime> dateBox = await Hive.openBox<DateTime>('dates');
|
||
Box<List<String>> listBox = await Hive.openBox<List<String>>('lists');
|
||
Box<Map<String, dynamic>> mapBox = await Hive.openBox<Map<String, dynamic>>('maps');
|
||
```
|
||
|
||
#### 2️⃣ 自定义类型(需要TypeAdapter)
|
||
```dart
|
||
// 自定义类
|
||
@HiveType(typeId: 0)
|
||
class Person {
|
||
@HiveField(0) final String name;
|
||
@HiveField(1) final int age;
|
||
|
||
const Person({required this.name, required this.age});
|
||
}
|
||
|
||
// 注册TypeAdapter
|
||
Hive.registerAdapter(PersonAdapter());
|
||
|
||
// 使用自定义类型
|
||
Box<Person> personBox = await Hive.openBox<Person>('persons');
|
||
|
||
// 添加自定义对象
|
||
await personBox.put('user1', Person(name: '张三', age: 25));
|
||
|
||
// 读取自定义对象
|
||
Person? user = personBox.get('user1');
|
||
```
|
||
|
||
### 📊 Hive性能优化
|
||
|
||
#### 1️⃣ 延迟初始化
|
||
```dart
|
||
class LazyBoxManager {
|
||
Box<Book>? _booksBox;
|
||
|
||
Box<Book> get booksBox {
|
||
_booksBox ??= Hive.box<Book>('books');
|
||
return _booksBox!;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2️⃣ 事务操作
|
||
```dart
|
||
// 使用事务确保数据一致性
|
||
await Hive.openBox<String>('settings');
|
||
await settingsBox.putAll({
|
||
'operation1': 'value1',
|
||
'operation2': 'value2',
|
||
});
|
||
|
||
// 如果出现错误,操作会自动回滚
|
||
```
|
||
|
||
#### 3️⃣ 内存管理
|
||
```dart
|
||
// 及时关闭不再使用的Box
|
||
await tempBox.close();
|
||
|
||
// 定期压缩数据库
|
||
await Hive.compact();
|
||
```
|
||
|
||
### 🚨 常见问题和解决方案
|
||
|
||
#### 1️⃣ "Cannot write, unknown type" 错误
|
||
```dart
|
||
// ❌ 错误:未注册TypeAdapter
|
||
@HiveType(typeId: 0)
|
||
class Book { ... }
|
||
|
||
await bookBox.put('book1', myBook); // 会报错
|
||
|
||
// ✅ 正确:注册TypeAdapter
|
||
Hive.registerAdapter(BookAdapter());
|
||
await bookBox.put('book1', myBook); // 正常工作
|
||
```
|
||
|
||
#### 2️⃣ "Box is not open" 错误
|
||
```dart
|
||
// ❌ 错误:忘记打开Box
|
||
Box<Book> booksBox = Hive.box<Book>('books'); // 如果Box未打开会报错
|
||
|
||
// ✅ 正确:先打开Box
|
||
Box<Book> booksBox = await Hive.openBox<Book>('books');
|
||
|
||
// 或者使用惰性Box(会自动打开)
|
||
LazyBox<Book> lazyBox = Hive.lazyBox<Book>('books');
|
||
```
|
||
|
||
#### 3️⃣ 数据迁移
|
||
```dart
|
||
// 检查数据库版本
|
||
const int currentVersion = 2;
|
||
const int savedVersion = settingsBox.get('db_version', defaultValue: 0);
|
||
|
||
if (savedVersion < currentVersion) {
|
||
// 执行数据迁移
|
||
await migrateDatabase(savedVersion, currentVersion);
|
||
settingsBox.put('db_version', currentVersion);
|
||
}
|
||
```
|
||
|
||
### 🔧 Hive开发工具
|
||
|
||
#### 1️⃣ 调试技巧
|
||
```dart
|
||
// 查看Box内容
|
||
print('Box内容: ${settingsBox.toMap()}');
|
||
|
||
// 查看Box统计信息
|
||
print('Box长度: ${settingsBox.length}');
|
||
print('是否为空: ${settingsBox.isEmpty}');
|
||
print('所有键: ${settingsBox.keys}');
|
||
```
|
||
|
||
#### 2️⃣ 数据导出和导入
|
||
```dart
|
||
// 导出数据
|
||
Map<String, dynamic> exportData = settingsBox.toMap();
|
||
String jsonString = jsonEncode(exportData);
|
||
|
||
// 导入数据
|
||
Map<String, dynamic> importData = jsonDecode(jsonString);
|
||
await settingsBox.putAll(importData);
|
||
```
|
||
|
||
### 🎯 最佳实践
|
||
|
||
#### 1️⃣ 数据库结构设计
|
||
```dart
|
||
// 使用有意义的Box名称
|
||
Box<Book> booksBox = await Hive.openBox<Book>('books');
|
||
Box<Bookmark> bookmarksBox = await Hive.openBox<Bookmark>('bookmarks');
|
||
Box<String> settingsBox = await Hive.openBox<String>('app_settings');
|
||
|
||
// 使用一致的键命名规范
|
||
await settingsBox.put('user_theme', 'dark');
|
||
await settingsBox.put('user_font_size', 16);
|
||
await settingsBox.put('user_language', 'zh_CN');
|
||
```
|
||
|
||
#### 2️⃣ 错误处理
|
||
```dart
|
||
try {
|
||
await bookBox.put('book1', myBook);
|
||
} on HiveError catch (e) {
|
||
print('Hive错误: $e');
|
||
} catch (e) {
|
||
print('未知错误: $e');
|
||
}
|
||
```
|
||
|
||
#### 3️⃣ 资源管理
|
||
```dart
|
||
class DatabaseService {
|
||
static DatabaseService? _instance;
|
||
static DatabaseService get instance => _instance ??= DatabaseService._();
|
||
|
||
DatabaseService._();
|
||
|
||
Future<void> init() async {
|
||
await Hive.initFlutter();
|
||
// 注册TypeAdapter
|
||
// 打开Box
|
||
}
|
||
|
||
Future<void> close() async {
|
||
await Hive.close();
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 核心组件详解
|
||
|
||
### 1️⃣ DatabaseService (数据库服务层)
|
||
|
||
**📖 文件位置:** `lib/services/database_service.dart`
|
||
|
||
#### 单例模式 (Singleton Pattern)
|
||
|
||
```dart
|
||
class DatabaseService {
|
||
static DatabaseService? _instance; // 🏠 私有实例变量
|
||
static DatabaseService get instance => _instance ??= DatabaseService._(); // 🔑 公共访问方法
|
||
|
||
DatabaseService._(); // 🔒 私有构造函数
|
||
}
|
||
```
|
||
|
||
**🎯 为什么用单例?**
|
||
- **比喻:** 一个城市只有一个图书馆管理系统
|
||
- **原因:** 避免多个数据库连接导致的数据冲突
|
||
- **好处:**
|
||
- 内存中只有一个实例
|
||
- 全局访问点
|
||
- 数据一致性
|
||
|
||
**💡 语法解释:**
|
||
- `static DatabaseService? _instance` - 类级别的私有变量
|
||
- `??=` - 空赋值操作符 (如果为null就赋新值)
|
||
- `DatabaseService._()` - 私有命名构造函数
|
||
|
||
#### Box概念
|
||
|
||
```dart
|
||
late Box<Book> _booksBox; // 📦 书籍存储盒子
|
||
```
|
||
|
||
**📦 Box是什么?**
|
||
- Box是Hive的存储容器
|
||
- 就像贴着标签的储物箱
|
||
- 每个Box只能存储一种类型的数据
|
||
- `_booksBox`专门存储Book对象
|
||
|
||
#### 数据库初始化过程
|
||
|
||
```dart
|
||
Future<void> init() async {
|
||
try {
|
||
// 第1步:获取应用存储位置
|
||
final appDocumentDir = await getApplicationDocumentsDirectory();
|
||
|
||
// 第2步:初始化Hive
|
||
await Hive.initFlutter(appDocumentDir.path);
|
||
|
||
// 第3步:注册TypeAdapter
|
||
Hive.registerAdapter(BookFormatAdapter());
|
||
Hive.registerAdapter(ReadingStatusAdapter());
|
||
Hive.registerAdapter(BookAdapter());
|
||
|
||
// 第4步:打开Box
|
||
_booksBox = await Hive.openBox<Book>('books');
|
||
|
||
} catch (e) {
|
||
// 错误处理
|
||
}
|
||
}
|
||
```
|
||
|
||
**🚶♂️ 详细步骤解释:**
|
||
|
||
1. **获取存储位置:**
|
||
- Android: `/data/data/com.yourapp/files/`
|
||
- iOS: `应用沙盒/Documents/`
|
||
- 这是系统为应用分配的专属存储空间
|
||
|
||
2. **初始化Hive:**
|
||
- 告诉Hive在哪个路径下创建数据库文件
|
||
- 只需要执行一次
|
||
|
||
3. **注册TypeAdapter:**
|
||
- 注册Book类型的"翻译官"
|
||
- 让Hive能理解我们的自定义对象
|
||
|
||
4. **打开Box:**
|
||
- 创建或打开名为'books'的存储容器
|
||
- 准备存储Book对象
|
||
|
||
### 2️⃣ BookRepository (数据访问层)
|
||
|
||
**📖 文件位置:** `lib/services/book_repository.dart`
|
||
|
||
#### Repository模式
|
||
|
||
**🏭 工厂比喻:**
|
||
- **DatabaseService** = 工厂大门(唯一入口)
|
||
- **BookRepository** = 书籍车间(专门处理书籍)
|
||
- **Box** = 货架(存储数据的地方)
|
||
- **Book对象** = 书籍产品
|
||
|
||
#### CRUD操作详解
|
||
|
||
**📖 C - Create (创建/添加)**
|
||
```dart
|
||
Future<void> addBook(Book book) async {
|
||
try {
|
||
final booksBox = _databaseService.getBooksBox(); // 📦 获取存储盒子
|
||
await booksBox.put(book.id, book); // 📝 存储书籍 (ID作为key)
|
||
print('✅ 书籍添加成功: ${book.title}');
|
||
} catch (e) {
|
||
print('❌ 添加书籍失败: $e');
|
||
rethrow;
|
||
}
|
||
}
|
||
```
|
||
|
||
**💡 关键概念:**
|
||
- `book.id` - 唯一标识符,作为存储的key
|
||
- `book` - 整个Book对象作为value
|
||
- 如果key已存在,会覆盖更新(保证数据唯一性)
|
||
|
||
**📖 R - Read (读取/查询)**
|
||
```dart
|
||
// 读取所有书籍
|
||
Future<List<Book>> getAllBooks() async {
|
||
try {
|
||
final booksBox = _databaseService.getBooksBox();
|
||
return booksBox.values.toList(); // 📋 获取所有值并转换为列表
|
||
} catch (e) {
|
||
// 错误处理
|
||
}
|
||
}
|
||
|
||
// 根据ID读取特定书籍
|
||
Future<Book?> getBookById(String id) async {
|
||
try {
|
||
final booksBox = _databaseService.getBooksBox();
|
||
return booksBox.get(id); // 🔍 根据key查找value
|
||
} catch (e) {
|
||
// 错误处理
|
||
}
|
||
}
|
||
```
|
||
|
||
**💡 语法解释:**
|
||
- `.values` - 获取Box中所有值
|
||
- `.get(id)` - 根据key获取特定值
|
||
- `Book?` - 可空类型(可能找不到)
|
||
|
||
**📖 U - Update (更新)**
|
||
```dart
|
||
Future<void> updateBook(Book book) async {
|
||
try {
|
||
final booksBox = _databaseService.getBooksBox();
|
||
await booksBox.put(book.id, book); // 🔄 覆盖存储同一ID的书籍
|
||
} catch (e) {
|
||
// 错误处理
|
||
}
|
||
}
|
||
```
|
||
|
||
**💡 更新原理:**
|
||
- 使用相同的`put(key, value)`方法
|
||
- 由于key相同,会覆盖原有数据
|
||
- 达到更新的效果
|
||
|
||
**📖 D - Delete (删除)**
|
||
```dart
|
||
Future<void> deleteBook(String id) async {
|
||
try {
|
||
final booksBox = _databaseService.getBooksBox();
|
||
await booksBox.delete(id); // 🗑️ 根据ID删除书籍
|
||
} catch (e) {
|
||
// 错误处理
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔢 TypeAdapter详解
|
||
|
||
### 什么是TypeAdapter?
|
||
|
||
**TypeAdapter = 类型转换器 = 翻译官**
|
||
|
||
**🤔 为什么需要TypeAdapter?**
|
||
- Hive只能存储基本数据类型(String、int、double、bool等)
|
||
- Hive不知道如何存储自定义对象(如Book)
|
||
- TypeAdapter负责"翻译"工作
|
||
|
||
**🔄 工作流程:**
|
||
```
|
||
存储时:Book对象 → TypeAdapter → 二进制数据 → 文件
|
||
读取时:文件 → 二进制数据 → TypeAdapter → Book对象
|
||
```
|
||
|
||
### 自动生成的TypeAdapter
|
||
|
||
**📖 文件位置:** `lib/models/book.g.dart` (自动生成)
|
||
|
||
#### read方法 (从文件读取)
|
||
```dart
|
||
@override
|
||
Book read(BinaryReader reader) {
|
||
final numOfFields = reader.readByte(); // 读取字段总数
|
||
final fields = <int, dynamic>{
|
||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||
};
|
||
return Book(
|
||
id: fields[0] as String, // 第0个字段 → id
|
||
title: fields[1] as String, // 第1个字段 → title
|
||
author: fields[2] as String?, // 第2个字段 → author (可为空)
|
||
publisher: fields[3] as String?, // 第3个字段 → publisher (可为空)
|
||
description: fields[4] as String?, // ... 依此类推
|
||
// ... 其他字段
|
||
);
|
||
}
|
||
```
|
||
|
||
**💡 工作原理:**
|
||
1. `BinaryReader` 从二进制文件读取数据
|
||
2. 读取字段总数
|
||
3. 按顺序读取每个字段
|
||
4. 转换为对应的Dart类型
|
||
5. 构造Book对象
|
||
|
||
#### write方法 (写入文件)
|
||
```dart
|
||
@override
|
||
void write(BinaryWriter writer, Book obj) {
|
||
writer
|
||
..writeByte(14) // 写入字段总数 (14个字段)
|
||
..writeByte(0)..write(obj.id) // 写入第0个字段:id
|
||
..writeByte(1)..write(obj.title) // 写入第1个字段:title
|
||
..writeByte(2)..write(obj.author) // 写入第2个字段:author
|
||
// ... 写入所有其他字段
|
||
}
|
||
```
|
||
|
||
**💡 工作原理:**
|
||
1. `BinaryWriter` 准备写入二进制文件
|
||
2. 写入字段总数
|
||
3. 按顺序写入每个字段的值
|
||
4. 最终存储为二进制格式
|
||
|
||
### 枚举TypeAdapter
|
||
|
||
**🔄 枚举为什么也需要TypeAdapter?**
|
||
- 枚举在Dart中也是自定义类型
|
||
- Hive需要知道如何存储和读取枚举值
|
||
- 枚举通常转换为整数存储
|
||
|
||
**💡 存储方式:**
|
||
- `BookFormat.epub` → 存储为整数 0
|
||
- `BookFormat.mobi` → 存储为整数 1
|
||
- `BookFormat.txt` → 存储为整数 2
|
||
- 读取时:0 → `BookFormat.epub`
|
||
|
||
---
|
||
|
||
## 🧪 测试验证详解
|
||
|
||
### 测试代码分析
|
||
**📖 文件位置:** `lib/main.dart`
|
||
|
||
#### 主函数初始化
|
||
```dart
|
||
void main() async {
|
||
WidgetsFlutterBinding.ensureInitialized(); // 🚦 确保Flutter引擎就绪
|
||
|
||
await DatabaseService.instance.init(); // 🏗️ 初始化数据库
|
||
await runBookRepositoryTest(); // 🧪 运行测试
|
||
|
||
runApp(const MyApp()); // 🎬 启动UI
|
||
}
|
||
```
|
||
|
||
**💡 关键概念:**
|
||
- `async/await` - 异步操作,因为数据库初始化需要时间
|
||
- `WidgetsFlutterBinding.ensureInitialized()` - 确保Flutter引擎准备就绪
|
||
|
||
#### 测试流程详解
|
||
|
||
**🧪 完整测试步骤:**
|
||
|
||
1. **获取当前数据状态**
|
||
```dart
|
||
List<Book> currentBooks = await bookRepository.getAllBooks();
|
||
print('📚 当前书籍数量: ${currentBooks.length}');
|
||
```
|
||
|
||
2. **创建测试数据**
|
||
```dart
|
||
final testBook = Book(
|
||
id: 'test_book_001', // 🔑 唯一标识符
|
||
title: 'Flutter开发入门', // 📚 书名
|
||
format: BookFormat.epub, // 📄 文件格式
|
||
status: ReadingStatus.pending, // 📖 阅读状态
|
||
// ... 其他字段
|
||
);
|
||
```
|
||
|
||
3. **测试添加功能**
|
||
```dart
|
||
await bookRepository.addBook(testBook); // ➕ 添加到数据库
|
||
```
|
||
|
||
4. **验证添加结果**
|
||
```dart
|
||
List<Book> updatedBooks = await bookRepository.getAllBooks();
|
||
print('📚 添加后书籍数量: ${updatedBooks.length}'); // 应该是 +1
|
||
```
|
||
|
||
5. **测试查询功能**
|
||
```dart
|
||
Book? foundBook = await bookRepository.getBookById(testBook.id);
|
||
// 验证找到的书籍信息是否正确
|
||
```
|
||
|
||
6. **测试更新功能**
|
||
```dart
|
||
final updatedBook = testBook.copyWith(status: ReadingStatus.completed);
|
||
await bookRepository.updateBook(updatedBook); // ✏️ 更新状态
|
||
```
|
||
|
||
7. **验证更新结果**
|
||
```dart
|
||
Book? updatedFoundBook = await bookRepository.getBookById(testBook.id);
|
||
// 检查状态是否真的变成了completed
|
||
```
|
||
|
||
**📊 测试验证的关键点:**
|
||
|
||
| 测试项 | 验证内容 | 预期结果 |
|
||
|-------|---------|---------|
|
||
| **存储功能** | 数据是否正确保存到文件 | ✅ 关闭应用后数据依然存在 |
|
||
| **查询功能** | 能否正确检索数据 | ✅ 返回正确的Book对象 |
|
||
| **更新功能** | 数据更新是否生效 | ✅ 更新后的数据正确保存 |
|
||
| **对象完整性** | 复杂对象是否完整保存 | ✅ 枚举、日期、列表等字段正确 |
|
||
| **类型安全** | 类型转换是否正确 | ✅ 没有类型转换错误 |
|
||
|
||
---
|
||
|
||
## 🔧 常见问题和解决方案
|
||
|
||
### 1️⃣ TypeAdapter错误
|
||
```
|
||
HiveError: Cannot write, unknown type: BookFormat. Did you forget to register an adapter?
|
||
```
|
||
|
||
**🔧 解决方案:**
|
||
```dart
|
||
// 在DatabaseService.init()中注册所有TypeAdapter
|
||
Hive.registerAdapter(BookFormatAdapter());
|
||
Hive.registerAdapter(ReadingStatusAdapter());
|
||
Hive.registerAdapter(BookAdapter());
|
||
```
|
||
|
||
**💡 错误原因:**
|
||
- Hive不知道如何序列化自定义类型
|
||
- 需要先注册TypeAdapter再使用该类型
|
||
|
||
### 2️⃣ Box未打开错误
|
||
```
|
||
Exception: Book Box未打开,请先调用init()
|
||
```
|
||
|
||
**🔧 解决方案:**
|
||
- 确保先调用 `DatabaseService.instance.init()`
|
||
- 不要在init()完成前访问Box
|
||
- 检查初始化顺序是否正确
|
||
|
||
### 3️⃣ 应用启动顺序
|
||
```dart
|
||
void main() async {
|
||
// 正确的初始化顺序
|
||
WidgetsFlutterBinding.ensureInitialized(); // 1. Flutter引擎
|
||
await DatabaseService.instance.init(); // 2. 数据库
|
||
runApp(const MyApp()); // 3. UI
|
||
}
|
||
```
|
||
|
||
### 4️⃣ TypeAdapter重新生成
|
||
```bash
|
||
# 清理并重新生成
|
||
dart run build_runner clean
|
||
dart run build_runner build --delete-conflicting-outputs
|
||
```
|
||
|
||
**🔄 何时需要重新生成:**
|
||
- 修改了数据模型(添加/删除字段)
|
||
- 更改了Hive注解
|
||
- 修改了枚举定义
|
||
|
||
---
|
||
|
||
## 🎯 实际应用场景
|
||
|
||
### 📚 电子书阅读器中的应用
|
||
|
||
**书籍管理:**
|
||
- 用户导入的EPUB、MOBI、TXT文件信息持久化
|
||
- 书籍封面、元数据存储
|
||
- 阅读进度保存
|
||
|
||
**阅读功能:**
|
||
- 书签位置记录
|
||
- 高亮文本保存
|
||
- 批注内容存储
|
||
|
||
**用户设置:**
|
||
- 字体大小、主题偏好
|
||
- 阅读习惯配置
|
||
- 书架分类管理
|
||
|
||
### 🔄 数据持久化在Readful项目中的应用
|
||
|
||
```dart
|
||
// 1. 用户导入电子书
|
||
final book = Book(
|
||
title: '深入Flutter',
|
||
author: '张三',
|
||
filePath: selectedFile.path,
|
||
format: detectFileFormat(selectedFile.path),
|
||
status: ReadingStatus.pending,
|
||
addedDate: DateTime.now(),
|
||
);
|
||
await bookRepository.addBook(book);
|
||
|
||
// 2. 用户开始阅读
|
||
final updatedBook = book.copyWith(
|
||
status: ReadingStatus.reading,
|
||
lastReadDate: DateTime.now(),
|
||
);
|
||
await bookRepository.updateBook(updatedBook);
|
||
|
||
// 3. 用户添加书签
|
||
final bookmark = Bookmark(
|
||
bookId: book.id,
|
||
title: '重要章节',
|
||
position: 0.75, // 75%位置
|
||
content: 'Flutter是Google开发的跨平台框架',
|
||
);
|
||
await bookmarkRepository.addBookmark(bookmark);
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 性能优化建议
|
||
|
||
### 1️⃣ 异步操作优化
|
||
|
||
```dart
|
||
// ❌ 不要这样写(阻塞UI)
|
||
List<Book> books = bookRepository.getAllBooks(); // 同步操作
|
||
|
||
// ✅ 应该这样写(不阻塞UI)
|
||
List<Book> books = await bookRepository.getAllBooks(); // 异步操作
|
||
```
|
||
|
||
### 2️⃣ 错误处理最佳实践
|
||
|
||
```dart
|
||
try {
|
||
await bookRepository.addBook(book);
|
||
} on HiveException catch (e) {
|
||
// 处理Hive特定错误
|
||
print('数据库错误: ${e.message}');
|
||
} catch (e) {
|
||
// 处理其他错误
|
||
print('未知错误: $e');
|
||
}
|
||
```
|
||
|
||
### 3️⃣ 内存管理
|
||
|
||
```dart
|
||
// 🔄 及时关闭数据库连接
|
||
@override
|
||
void dispose() {
|
||
DatabaseService.instance.close();
|
||
super.dispose();
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎓 学习成果检验
|
||
|
||
### ✅ 掌握的核心知识点
|
||
|
||
1. **数据持久化概念** ⭐⭐⭐⭐⭐
|
||
- 理解为什么需要数据持久化
|
||
- 知道不同存储方案的优缺点
|
||
|
||
2. **Hive数据库原理** ⭐⭐⭐⭐⭐
|
||
- 单例模式的应用
|
||
- Box概念的理解
|
||
- TypeAdapter的自动生成机制
|
||
|
||
3. **Repository模式** ⭐⭐⭐⭐
|
||
- 数据访问层的抽象
|
||
- CRUD操作的实现
|
||
- 错误处理最佳实践
|
||
|
||
4. **异步编程** ⭐⭐⭐⭐
|
||
- async/await的使用
|
||
- Future类型的概念
|
||
- 异步操作的错误处理
|
||
|
||
5. **代码生成工具** ⭐⭐⭐⭐
|
||
- build_runner的使用
|
||
- TypeAdapter的自动生成
|
||
- 代码维护的最佳实践
|
||
|
||
### 🚀 实际应用能力
|
||
|
||
- ✅ **数据库设计:** 能够设计并实现移动应用的本地数据存储
|
||
- ✅ **数据操作:** 熟练使用Hive进行数据的增删改查
|
||
- ✅ **架构设计:** 理解Repository模式的应用场景
|
||
- ✅ **问题解决:** 能够独立排查和解决数据持久化相关的问题
|
||
|
||
---
|
||
|
||
## 📚 进阶学习建议
|
||
|
||
### 🔮 下一步学习内容
|
||
|
||
1. **性能优化**
|
||
- 大量数据的分页加载
|
||
- 数据库查询优化
|
||
- 内存管理策略
|
||
|
||
2. **高级Hive功能**
|
||
- 复杂查询和索引
|
||
- 数据迁移和版本管理
|
||
- 备份和恢复机制
|
||
|
||
3. **状态管理集成**
|
||
- Provider状态管理
|
||
- Riverpod状态管理
|
||
- 数据与UI的响应式绑定
|
||
|
||
4. **其他存储方案**
|
||
- SQLite与Hive的对比
|
||
- 云存储集成
|
||
- 数据同步策略
|
||
|
||
---
|
||
|
||
## 🎉 章节总结
|
||
|
||
恭喜你!你已经掌握了Flutter中最重要的技能之一:**数据持久化**!
|
||
|
||
通过本章学习,你获得了:
|
||
|
||
1. **🏗️ 完整的数据持久化架构**
|
||
2. **🔧 实际的编码技能**
|
||
3. **💡 设计模式的理解**
|
||
4. **🐛 问题解决能力**
|
||
5. **🚀 项目实践经验**
|
||
|
||
**🎯 关键成就:**
|
||
- ✅ 成功实现了Readful项目的完整数据层
|
||
- ✅ 掌握了Hive数据库的核心使用方法
|
||
- ✅ 理解了Repository模式的实际应用
|
||
- ✅ 具备了独立开发移动应用数据持久化的能力
|
||
|
||
这些知识点将是你Flutter开发路上的重要基石,无论开发什么类型的应用,数据持久化都是必备技能!
|
||
|
||
**📖 下一步:** 让我们基于这个强大的数据基础,开始实现用户界面功能! |