قاعدة بيانات Hive NoSQL: الاستخدام المتقدم والأداء
قاعدة بيانات Hive NoSQL: الاستخدام المتقدم والأداء
في الدرس السابق تعلمت كيفية فتح الصناديق (boxes) وتسجيل المحولات (adapters) وإجراء عمليات CRUD الأساسية مع Hive. يتعمق هذا الدرس في أربعة موضوعات على مستوى الإنتاج: واجهة المستخدم التفاعلية مع ValueListenableBuilder، واستراتيجيات الضغط (compaction) لاستعادة مساحة القرص المهدرة، والصناديق المشفرة لتخزين البيانات الحساسة، ومقارنة الأداء الصادقة مع SQLite. إتقان هذه الموضوعات هو الفرق بين نموذج أولي وتطبيق جاهز للنشر.
1. واجهة المستخدم التفاعلية مع ValueListenableBuilder
كل صندوق Hive مفتوح يعرض ValueListenable<Box> عبر طريقة listenable(). إحاطة ودجت بـ ValueListenableBuilder يجعله يعيد البناء تلقائياً عند تغيير أي مفتاح في ذلك الصندوق — دون setState أو StreamBuilder أو أي مدير حالة خارجي.
قائمة مهام تفاعلية مع ValueListenableBuilder
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
class TaskListScreen extends StatelessWidget {
const TaskListScreen({super.key});
@override
Widget build(BuildContext context) {
final box = Hive.box<String>('tasks');
return Scaffold(
appBar: AppBar(title: const Text('مهامي')),
body: ValueListenableBuilder<Box<String>>(
valueListenable: box.listenable(),
builder: (context, box, _) {
if (box.isEmpty) {
return const Center(child: Text('لا توجد مهام بعد.'));
}
return ListView.builder(
itemCount: box.length,
itemBuilder: (context, index) {
final task = box.getAt(index) ?? '';
return ListTile(
title: Text(task),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => box.deleteAt(index),
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => box.add('مهمة ${box.length + 1}'),
child: const Icon(Icons.add),
),
);
}
}
يمكنك أيضاً تمرير قائمة keys إلى listenable(keys: ['profile']) لتقييد عمليات إعادة البناء على مفاتيح محددة، مما يمنع إعادة الرسم غير الضرورية في الصناديق الكبيرة.
ValueListenableBuilder على StreamBuilder لصناديق Hive. فهو متزامن (بدون overhead غير متزامن)، لديه دائماً قيمة أولية، ولا يصدر أخطاء — مما يجعل كود واجهة المستخدم أبسط وأكثر أماناً.2. استراتيجيات الضغط (Compaction)
يستخدم Hive سجلاً يُلحق فقط (append-only log) على القرص. كل عملية كتابة — بما في ذلك الحذف والتحديث — تُلحق إدخالاً جديداً. مع مرور الوقت، يتراكم الملف بإدخالات ميتة وينمو بشكل أكبر بكثير من حجم البيانات الفعلية. الضغط (Compaction) يعيد كتابة الملف مع الاحتفاظ بالقيم الحية فقط.
تتحكم في الضغط عن طريق تمرير رد نداء CompactionStrategy عند فتح صندوق:
استراتيجية ضغط مخصصة
await Hive.openBox<Map>(
'analytics',
compactionStrategy: (entries, deletedEntries) {
// الضغط عندما تتجاوز الإدخالات المحذوفة 50
// أو عندما يتجاوز إجمالي عدد الإدخالات 500
return deletedEntries > 50 || entries > 500;
},
);
يستقبل رد النداء entries (إجمالي السجلات في الملف، بما فيها الميتة) وdeletedEntries (السجلات التي تم الكتابة فوقها أو حذفها). أعد true لتشغيل الضغط. الاستراتيجية الافتراضية تضغط فقط عندما يكون deletedEntries > 60 وفي نفس الوقت deletedEntries / entries > 0.5.
await box.compact() يدوياً على isolate خلفي لتجنب التقطع (jank) على الخيط الرئيسي.3. الصناديق المشفرة مع HiveCipher
يدعم Hive تشفير AES-256-GCM من خلال واجهة HiveCipher. يقبل HiveAesCipher المدمج مفتاحاً من 32 بايت. يجب توليد هذا المفتاح مرة واحدة، وتخزينه في سلسلة مفاتيح نظام التشغيل (عبر flutter_secure_storage)، وتمريره في كل مرة تفتح فيها الصندوق.
فتح صندوق مشفر
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive_flutter/hive_flutter.dart';
Future<Box<String>> openSecureBox() async {
const secureStorage = FlutterSecureStorage();
// استرداد أو توليد مفتاح AES من 32 بايت
String? encodedKey = await secureStorage.read(key: 'hive_key');
if (encodedKey == null) {
final key = Hive.generateSecureKey(); // تعيد List<int> من 32 بايت
encodedKey = base64UrlEncode(key);
await secureStorage.write(key: 'hive_key', value: encodedKey);
}
final keyBytes = base64Url.decode(encodedKey);
return Hive.openBox<String>(
'secrets',
encryptionCipher: HiveAesCipher(keyBytes),
);
}
4. Hive مقابل SQLite — مقايضات الأداء
يسوّق Hive نفسه باعتباره أسرع متجر مفتاح-قيمة في Flutter، والمعايير تؤكد أنه يتفوق على SQLite في القراءات والكتابات البسيطة. الدقة تكمن في ما تقيسه بالضبط:
- قراءات السجل الواحد: Hive أسرع بـ ~2–5× لأنه يعين الملف في الذاكرة ويُفرز (deserialize) مباشرة دون تحليل SQL.
- الإدخالات الجماعية: Hive أسرع لإلحاق السجلات؛ يحصل SQLite على الأفضلية عند استخدام المعاملات للكتابات المُجمَّعة.
- الاستعلامات المعقدة: SQLite يفوز بشكل حاسم. لا يوجد لغة استعلام في Hive — يجب تحميل جميع السجلات والتصفية في Dart، وهو O(n).
- العلاقات: يدعم SQLite المفاتيح الخارجية وJOINs بشكل أصلي. يتطلب Hive إدارة مراجع يدوية.
- مجموعات البيانات الكبيرة: يتوسع B-tree index في SQLite لملايين الصفوف. يحتفظ Hive بالصندوق بالكامل في الذاكرة، مما يجعله غير مناسب لمجموعات البيانات الأكبر من RAM المتاح.
sqflite أو drift) عندما تحتاج إلى التصفية والترتيب والعلاقات أو مجموعات البيانات التي قد تتجاوز بضعة آلاف من السجلات.الخلاصة
يمنحك الاستخدام المتقدم لـ Hive أنماطاً تفاعلية قوية وأماناً للتخزين دون مغادرة نظام Dart البيئي. يزيل ValueListenableBuilder الكود المتكرر من خلال إعادة بناء واجهة المستخدم تلقائياً عند تغييرات الصندوق. يبقي الضغط أحجام الملفات تحت السيطرة في السيناريوهات كثيفة الكتابة. تحمي الصناديق المشفرة البيانات الحساسة باستخدام AES-256 مع مفاتيح مخزنة في سلسلة مفاتيح نظام التشغيل. فهم مقايضات Hive مقابل SQLite يتيح لك اختيار الأداة الصحيحة لكل جزء من تطبيقك — وغالباً كلتاهما في نفس المشروع.